e2e tests

This commit is contained in:
Denys Vuika 2017-10-19 13:47:25 +01:00
parent aba476c15f
commit 7f12841e5a
45 changed files with 3213 additions and 26 deletions

View File

@ -1,14 +0,0 @@
import { AppPage } from './app.po';
describe('alfresco-content-app App', () => {
let page: AppPage;
beforeEach(() => {
page = new AppPage();
});
it('should display welcome message', () => {
page.navigateTo();
expect(page.getParagraphText()).toEqual('Welcome to app!');
});
});

View File

@ -1,11 +0,0 @@
import { browser, by, element } from 'protractor';
export class AppPage {
navigateTo() {
return browser.get('/');
}
getParagraphText() {
return element(by.css('app-root h1')).getText();
}
}

View File

@ -0,0 +1,35 @@
/*!
* @license
* Copyright 2017 Alfresco Software, Ltd.
*
* 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 { ElementFinder, element, by, ExpectedConditions as EC, browser } from 'protractor';
import { BROWSER_WAIT_TIMEOUT } from '../configs';
export abstract class Component {
component: ElementFinder;
constructor(selector: string, ancestor?: ElementFinder) {
const locator = by.css(selector);
this.component = ancestor
? ancestor.element(locator)
: element(locator);
}
wait() {
return browser.wait(EC.presenceOf(this.component), BROWSER_WAIT_TIMEOUT);
}
}

View File

@ -0,0 +1,24 @@
/*!
* @license
* Copyright 2017 Alfresco Software, Ltd.
*
* 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 * from './login/login';
export * from './header/header';
export * from './header/user-info';
export * from './data-table/data-table';
export * from './pagination/pagination';
export * from './sidenav/sidenav';
export * from './toolbar/toolbar';

View File

@ -0,0 +1,110 @@
/*!
* @license
* Copyright 2017 Alfresco Software, Ltd.
*
* 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 { ElementFinder, ElementArrayFinder, promise, by, browser, ExpectedConditions as EC } from 'protractor';
import { BROWSER_WAIT_TIMEOUT } from '../../configs';
import { Component } from '../component';
export class DataTable extends Component {
private static selectors = {
root: 'adf-datatable',
head: 'table > thead',
columnHeader: 'tr > th',
sortedColumnHeader: `
th.adf-data-table__header--sorted-asc,
th.adf-data-table__header--sorted-desc
`,
body: 'table > tbody',
row: 'tr'
};
private head: ElementFinder = this.component.element(by.css(DataTable.selectors.head));
private body: ElementFinder = this.component.element(by.css(DataTable.selectors.body));
constructor(ancestor?: ElementFinder) {
super(DataTable.selectors.root, ancestor);
}
// Wait methods (waits for elements)
waitForHeader() {
return browser.wait(EC.presenceOf(this.head), BROWSER_WAIT_TIMEOUT);
}
// waitForEmptyState() {}
// Header/Column methods
getColumnHeaders(): ElementArrayFinder {
const locator = by.css(DataTable.selectors.columnHeader);
return this.head.all(locator);
}
getNthColumnHeader(nth: number): ElementFinder {
return this.getColumnHeaders().get(nth - 1);
}
getColumnHeaderByLabel(label: string): ElementFinder {
const locator = by.cssContainingText(DataTable.selectors.columnHeader, label);
return this.head.element(locator);
}
getSortedColumnHeader(): ElementFinder {
const locator = by.css(DataTable.selectors.sortedColumnHeader);
return this.head.element(locator);
}
sortByColumn(columnName: string): Promise<void> {
const column = this.getColumnHeaderByLabel(columnName);
const click = browser.actions().mouseMove(column).click();
return click.perform();
}
// Rows methods
getRows(): ElementArrayFinder {
return this.body.all(by.css(DataTable.selectors.row));
}
getNthRow(nth: number): ElementFinder {
return this.getRows().get(nth - 1);
}
getRowByContainingText(text: string): ElementFinder {
const locator = by.cssContainingText(DataTable.selectors.row, text);
return this.body.element(locator);
}
countRows(): promise.Promise<number> {
return this.getRows().count();
}
// Navigation/selection methods
doubleClickOnRowByContainingText(text: string): promise.Promise<void> {
const row = this.getRowByContainingText(text);
const dblClick = browser.actions().mouseMove(row).click().click();
return dblClick.perform();
}
clickOnRowByContainingText(text: string): promise.Promise<void> {
const row = this.getRowByContainingText(text);
const click = browser.actions().mouseMove(row).click();
return click.perform();
}
}

View File

@ -0,0 +1,106 @@
/*!
* @license
* Copyright 2017 Alfresco Software, Ltd.
*
* 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 { ElementFinder, by, browser, protractor, ExpectedConditions as EC } from 'protractor';
import { BROWSER_WAIT_TIMEOUT } from '../../configs';
import { Component } from '../component';
export class CreateOrEditFolderDialog extends Component {
private static selectors = {
root: '.mat-dialog-container',
title: '.mat-dialog-title',
nameInput: '.mat-dialog-container .mat-input-element[placeholder="Name"]',
descriptionTextArea: '.mat-dialog-container .mat-input-element[placeholder="Description"]',
button: '.mat-dialog-actions button',
validationMessage: '.mat-hint span'
};
title: ElementFinder = this.component.element(by.css(CreateOrEditFolderDialog.selectors.title));
nameInput: ElementFinder = this.component.element(by.css(CreateOrEditFolderDialog.selectors.nameInput));
descriptionTextArea: ElementFinder = this.component.element(by.css(CreateOrEditFolderDialog.selectors.descriptionTextArea));
createButton: ElementFinder = this.component.element(by.cssContainingText(CreateOrEditFolderDialog.selectors.button, 'Create'));
cancelButton: ElementFinder = this.component.element(by.cssContainingText(CreateOrEditFolderDialog.selectors.button, 'Cancel'));
updateButton: ElementFinder = this.component.element(by.cssContainingText(CreateOrEditFolderDialog.selectors.button, 'Update'));
validationMessage: ElementFinder = this.component.element(by.css(CreateOrEditFolderDialog.selectors.validationMessage));
constructor(ancestor?: ElementFinder) {
super(CreateOrEditFolderDialog.selectors.root, ancestor);
}
waitForDialogToOpen() {
return browser.wait(EC.presenceOf(this.title), BROWSER_WAIT_TIMEOUT);
}
waitForDialogToClose() {
return browser.wait(EC.stalenessOf(this.title), BROWSER_WAIT_TIMEOUT);
}
getTitle(): Promise<string> {
return this.title.getText();
}
isValidationMessageDisplayed(): Promise<boolean> {
return this.validationMessage.isDisplayed();
}
getValidationMessage(): Promise<string> {
return this.isValidationMessageDisplayed()
.then(() => this.validationMessage.getText())
.catch(() => '');
}
enterName(name: string): CreateOrEditFolderDialog {
const { nameInput } = this;
nameInput.clear();
nameInput.sendKeys(name);
return this;
}
deleteNameWithBackspace(): Promise<void> {
const { nameInput } = this;
return nameInput.clear()
.then(() => {
return nameInput.sendKeys(' ', protractor.Key.CONTROL, 'a', protractor.Key.NULL, protractor.Key.BACK_SPACE);
});
}
enterDescription(description: string): CreateOrEditFolderDialog {
const { descriptionTextArea } = this;
descriptionTextArea.clear();
descriptionTextArea.sendKeys(description);
return this;
}
clickCreate() {
return this.createButton.click();
}
clickCancel() {
return this.cancelButton.click()
.then(() => this.waitForDialogToClose());
}
clickUpdate() {
return this.updateButton.click();
}
}

View File

@ -0,0 +1,34 @@
/*!
* @license
* Copyright 2017 Alfresco Software, Ltd.
*
* 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 { ElementFinder, by } from 'protractor';
import { Component } from '../component';
import { UserInfo } from './user-info';
export class Header extends Component {
private locators = {
logoLink: by.css('.mdl-layout-title'),
userInfo: by.css('app-current-user')
};
logoLink: ElementFinder = this.component.element(this.locators.logoLink);
userInfo: UserInfo = new UserInfo(this.component);
constructor(ancestor?: ElementFinder) {
super('app-header', ancestor);
}
}

View File

@ -0,0 +1,56 @@
/*!
* @license
* Copyright 2017 Alfresco Software, Ltd.
*
* 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 { ElementFinder, ElementArrayFinder, element, by, promise } from 'protractor';
import { Menu } from '../menu/menu';
import { Component } from '../component';
export class UserInfo extends Component {
private locators = {
avatar: by.css('.current-user__avatar'),
fullName: by.css('.current-user__full-name'),
menuItems: by.css('[md-menu-item]')
};
fullName: ElementFinder = this.component.element(this.locators.fullName);
avatar: ElementFinder = this.component.element(this.locators.avatar);
menu: Menu = new Menu();
constructor(ancestor?: ElementFinder) {
super('app-current-user', ancestor);
}
openMenu(): promise.Promise<Menu> {
const { menu, avatar } = this;
return avatar.click()
.then(() => menu.wait())
.then(() => menu);
}
get name(): Promise<string> {
return this.fullName.getText();
}
signOut(): Promise<void> {
return this.openMenu()
.then(menu => {
menu.clickMenuItem('Sign out');
});
}
}

View File

@ -0,0 +1,67 @@
/*!
* @license
* Copyright 2017 Alfresco Software, Ltd.
*
* 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 { by, ElementFinder } from 'protractor';
import { Component } from '../component';
export class LoginComponent extends Component {
static selector = 'alfresco-login';
private locators = {
usernameInput: by.css('input#username'),
passwordInput: by.css('input#password'),
submitButton: by.css('button#login-button'),
errorMessage: by.css('.login-error-message')
};
usernameInput: ElementFinder = this.component.element(this.locators.usernameInput);
passwordInput: ElementFinder = this.component.element(this.locators.passwordInput);
submitButton: ElementFinder = this.component.element(this.locators.submitButton);
errorMessage: ElementFinder = this.component.element(this.locators.errorMessage);
constructor(ancestor?: ElementFinder) {
super(LoginComponent.selector, ancestor);
}
enterUsername(username: string): LoginComponent {
const { usernameInput } = this;
usernameInput.clear();
usernameInput.sendKeys(username);
return this;
}
enterPassword(password: string): LoginComponent {
const { passwordInput } = this;
passwordInput.clear();
passwordInput.sendKeys(password);
return this;
}
enterCredentials(username: string, password: string) {
this.enterUsername(username).enterPassword(password);
return this;
}
submit(): Promise<void> {
return this.submitButton.click();
}
}

View File

@ -0,0 +1,57 @@
/*!
* @license
* Copyright 2017 Alfresco Software, Ltd.
*
* 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 { ElementFinder, ElementArrayFinder, by, browser, ExpectedConditions as EC } from 'protractor';
import { BROWSER_WAIT_TIMEOUT } from '../../configs';
import { Component } from '../component';
export class Menu extends Component {
private static selectors = {
root: '.mat-menu-panel',
item: 'button.mat-menu-item'
};
items: ElementArrayFinder = this.component.all(by.css(Menu.selectors.item));
constructor(ancestor?: ElementFinder) {
super(Menu.selectors.root, ancestor);
}
wait() {
return browser.wait(EC.visibilityOf(this.items.get(0)), BROWSER_WAIT_TIMEOUT);
}
getNthItem(nth: number): ElementFinder {
return this.items.get(nth - 1);
}
getItemByLabel(label: string): ElementFinder {
return this.component.element(by.cssContainingText(Menu.selectors.item, label));
}
getItemTooltip(label: string): Promise<string> {
return this.getItemByLabel(label).getAttribute('title');
}
clicktNthItem(nth: number): Promise<void> {
return this.getNthItem(nth).click();
}
clickMenuItem(label: string): Promise<void> {
return this.getItemByLabel(label).click();
}
}

View File

@ -0,0 +1,67 @@
/*!
* @license
* Copyright 2017 Alfresco Software, Ltd.
*
* 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 { ElementFinder, ElementArrayFinder, promise, by, browser, ExpectedConditions as EC } from 'protractor';
import { BROWSER_WAIT_TIMEOUT } from '../../configs';
import { Menu } from '../menu/menu';
import { Component } from '../component';
export class Pagination extends Component {
private static selectors = {
root: 'adf-pagination',
range: '.adf-pagination__range',
maxItems: '.adf-pagination__max-items',
currentPage: '.adf-pagination__current-page',
totalPages: '.adf-pagination__total-pages',
previousButton: '.adf-pagination__previous-button',
nextButton: '.adf-pagination__next-button',
maxItemsButton: '.adf-pagination__max-items + button[md-icon-button]',
pagesButton: '.adf-pagination__current-page + button[md-icon-button]'
};
range: ElementFinder = this.component.element(by.css(Pagination.selectors.range));
maxItems: ElementFinder = this.component.element(by.css(Pagination.selectors.maxItems));
currentPage: ElementFinder = this.component.element(by.css(Pagination.selectors.currentPage));
totalPages: ElementFinder = this.component.element(by.css(Pagination.selectors.totalPages));
previousButton: ElementFinder = this.component.element(by.css(Pagination.selectors.previousButton));
nextButton: ElementFinder = this.component.element(by.css(Pagination.selectors.nextButton));
maxItemsButton: ElementFinder = this.component.element(by.css(Pagination.selectors.maxItemsButton));
pagesButton: ElementFinder = this.component.element(by.css(Pagination.selectors.pagesButton));
menu: Menu = new Menu();
constructor(ancestor?: ElementFinder) {
super(Pagination.selectors.root, ancestor);
}
openMaxItemsMenu(): Promise<Menu> {
const { menu, maxItemsButton } = this;
return maxItemsButton.click()
.then(() => menu.wait())
.then(() => menu);
}
openCurrentPageMenu(): Promise<Menu> {
const { menu, pagesButton } = this;
return this.pagesButton.click()
.then(() => menu.wait())
.then(() => menu);
}
}

View File

@ -0,0 +1,63 @@
/*!
* @license
* Copyright 2017 Alfresco Software, Ltd.
*
* 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 { ElementFinder, ElementArrayFinder, by, promise } from 'protractor';
import { Menu } from '../menu/menu';
import { Component } from '../component';
export class Sidenav extends Component {
private static selectors = {
root: 'app-sidenav',
link: '.sidenav-menu__item-link',
activeLink: '.sidenav-menu__item-link--active',
newButton: '.sidenav__section--new__button'
};
links: ElementArrayFinder = this.component.all(by.css(Sidenav.selectors.link));
activeLink: ElementFinder = this.component.element(by.css(Sidenav.selectors.activeLink));
newButton: ElementArrayFinder = this.component.all(by.css(Sidenav.selectors.newButton));
menu: Menu = new Menu();
constructor(ancestor?: ElementFinder) {
super(Sidenav.selectors.root, ancestor);
}
openNewMenu(): promise.Promise<Menu> {
const { menu, newButton } = this;
return newButton.click()
.then(() => menu.wait())
.then(() => menu);
}
isActiveByLabel(label: string): promise.Promise<boolean> {
return this
.getLinkByLabel(label)
.getWebElement()
.then(element => element.getAttribute('class'))
.then(className => className.includes(Sidenav.selectors.activeLink.replace('.', '')));
}
getLinkByLabel(label: string): ElementFinder {
return this.component.element(by.cssContainingText(Sidenav.selectors.link, label));
}
navigateToLinkByLabel(label: string): promise.Promise<void> {
return this.getLinkByLabel(label).click();
}
}

View File

@ -0,0 +1,56 @@
/*!
* @license
* Copyright 2017 Alfresco Software, Ltd.
*
* 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 { ElementFinder, ElementArrayFinder, by, promise } from 'protractor';
import { Menu } from '../menu/menu';
import { Component } from '../component';
export class ToolbarActions extends Component {
private static selectors = {
root: 'adf-toolbar',
button: '.mat-icon-button'
};
menu: Menu = new Menu();
buttons: ElementArrayFinder = this.component.all(by.css(ToolbarActions.selectors.button));
constructor(ancestor?: ElementFinder) {
super(ToolbarActions.selectors.root, ancestor);
}
isEmpty(): Promise<boolean> {
return this.buttons.count().then(count => (count === 0));
}
getButtonByLabel(label: string): ElementFinder {
return this.component.element(by.cssContainingText(ToolbarActions.selectors.button, label));
}
getButtonByTitleAttribute(title: string): ElementFinder {
return this.component.element(by.css(`${ToolbarActions.selectors.button}[title="${title}"]`));
}
openMoreMenu(): promise.Promise<Menu> {
const { menu } = this;
const moreButton = this.getButtonByTitleAttribute('More actions');
return moreButton
.click()
.then(() => menu.wait())
.then(() => menu);
}
}

View File

@ -0,0 +1,37 @@
/*!
* @license
* Copyright 2017 Alfresco Software, Ltd.
*
* 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 { ElementFinder, ElementArrayFinder, by } from 'protractor';
import { Menu } from '../menu/menu';
import { Component } from '../component';
export class ToolbarBreadcrumb extends Component {
private static selectors = {
root: 'adf-breadcrumb',
item: '.adf-breadcrumb-item'
};
items: ElementArrayFinder = this.component.all(by.css(ToolbarBreadcrumb.selectors.item));
constructor(ancestor?: ElementFinder) {
super(ToolbarBreadcrumb.selectors.root, ancestor);
}
getNthItem(nth: number): ElementFinder {
return this.items.get(nth - 1);
}
}

View File

@ -0,0 +1,34 @@
/*!
* @license
* Copyright 2017 Alfresco Software, Ltd.
*
* 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 { ElementFinder, ElementArrayFinder, by } from 'protractor';
import { Component } from '../component';
import { ToolbarActions } from './toolbar-actions';
import { ToolbarBreadcrumb } from './toolbar-breadcrumb';
export class Toolbar extends Component {
private static selectors = {
root: '.inner-layout__header'
};
actions: ToolbarActions = new ToolbarActions(this.component);
breadcrumb: ToolbarBreadcrumb = new ToolbarBreadcrumb(this.component);
constructor(ancestor?: ElementFinder) {
super(Toolbar.selectors.root, ancestor);
}
}

53
e2e/configs.ts Normal file
View File

@ -0,0 +1,53 @@
export const BROWSER_RESOLUTION_WIDTH = 1200;
export const BROWSER_RESOLUTION_HEIGHT = 800;
export const BROWSER_WAIT_TIMEOUT = 10000;
// Application configs
export const APP_HOST = 'http://localhost:3000';
// Repository configs
export const REPO_API_HOST = 'http://localhost:8080';
export const REPO_API_TENANT = '-default-';
// Admin details
export const ADMIN_USERNAME = 'admin';
export const ADMIN_PASSWORD = 'admin';
export const ADMIN_FULL_NAME = 'Administrator';
// Application Routes
export const APP_ROUTES = {
FAVORITES: '/favorites',
FILE_LIBRARIES: '/libraries',
LOGIN: '/login',
LOGOUT: '/logout',
PERSONAL_FILES: '/personal-files',
RECENT_FILES: '/recent-files',
SHARED_FILES: '/shared',
TRASHCAN: '/trashcan'
};
// Sidebar labels
export const SIDEBAR_LABELS = {
PERSONAL_FILES: 'Personal Files',
FILE_LIBRARIES: 'File Libraries',
SHARED_FILES: 'Shared',
RECENT_FILES: 'Recent Files',
FAVORITES: 'Favorites',
TRASH: 'Trash'
};
// Site visibility
export const SITE_VISIBILITY = {
PUBLIC: 'PUBLIC',
MODERATED: 'MODERATED',
PRIVATE: 'PRIVATE'
};
// Site roles
export const SITE_ROLES = {
SITE_CONSUMER: 'SiteConsumer',
SITE_COLLABORATOR: 'SiteCollaborator',
SITE_CONTRIBUTOR: 'SiteContributor',
SITE_MANAGER: 'SiteManager'
};

View File

@ -0,0 +1,31 @@
/*!
* @license
* Copyright 2017 Alfresco Software, Ltd.
*
* 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 { Header, DataTable, Pagination, Toolbar, Sidenav } from '../components/components';
import { Page } from './page';
export class BrowsingPage extends Page {
header = new Header(this.app);
sidenav = new Sidenav(this.app);
toolbar = new Toolbar(this.app);
dataTable = new DataTable(this.app);
pagination = new Pagination(this.app);
signOut(): Promise<void> {
return this.header.userInfo.signOut();
}
}

54
e2e/pages/login-page.ts Normal file
View File

@ -0,0 +1,54 @@
/*!
* @license
* Copyright 2017 Alfresco Software, Ltd.
*
* 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 { browser, ExpectedConditions as EC } from 'protractor';
import { LoginComponent } from '../components/components';
import { Page } from './page';
import {
ADMIN_USERNAME,
ADMIN_PASSWORD,
BROWSER_WAIT_TIMEOUT,
APP_ROUTES
} from '../configs';
export class LoginPage extends Page {
login: LoginComponent = new LoginComponent(this.app);
/** @override */
constructor() {
super(APP_ROUTES.LOGIN);
}
/** @override */
load(): Promise<any> {
return super.load().then(() => {
const { submitButton } = this.login;
const hasSumbitButton = EC.presenceOf(submitButton);
return browser.wait(hasSumbitButton, BROWSER_WAIT_TIMEOUT);
});
}
loginWith(username: string, password: string): Promise<void> {
return this.login.enterCredentials(username, password).submit();
}
loginWithAdmin(): Promise<any> {
return this.loginWith(ADMIN_USERNAME, ADMIN_PASSWORD);
}
}

31
e2e/pages/logout-page.ts Normal file
View File

@ -0,0 +1,31 @@
/*!
* @license
* Copyright 2017 Alfresco Software, Ltd.
*
* 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 { Page } from './page';
import { APP_ROUTES } from '../configs';
export class LogoutPage extends Page {
/** @override */
constructor() {
super(APP_ROUTES.LOGOUT);
}
/** @override */
load(): Promise<any> {
return super.load();
}
}

59
e2e/pages/page.ts Normal file
View File

@ -0,0 +1,59 @@
/*!
* @license
* Copyright 2017 Alfresco Software, Ltd.
*
* 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 { browser, element, by, ElementFinder } from 'protractor';
export abstract class Page {
private static USE_HASH_STRATEGY: boolean = true;
private locators = {
app: by.css('alfresco-content-app'),
overlay: by.css('.cdk-overlay-container'),
snackBar: by.css('simple-snack-bar')
};
public app: ElementFinder = element(this.locators.app);
public overlay: ElementFinder = element(this.locators.overlay);
public snackBar: ElementFinder = element(this.locators.snackBar);
constructor(public url: string = '') {}
get title(): Promise<string> {
return browser.getTitle();
}
load(relativeUrl: string = ''): Promise<void> {
const hash = Page.USE_HASH_STRATEGY ? '/#' : '';
const path = `${hash}${this.url}${relativeUrl}`;
return browser.get(path);
}
refresh(): Promise<void> {
return browser.refresh();
}
isSnackBarDisplayed(): Promise<boolean> {
return this.snackBar.isDisplayed();
}
getSnackBarMessage(): Promise<string> {
return this.isSnackBarDisplayed()
.then(() => this.snackBar.getText())
.catch(() => '');
}
}

20
e2e/pages/pages.ts Normal file
View File

@ -0,0 +1,20 @@
/*!
* @license
* Copyright 2017 Alfresco Software, Ltd.
*
* 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 * from './browsing-page';
export * from './login-page';
export * from './logout-page';

View File

@ -0,0 +1,277 @@
/*!
* @license
* Copyright 2017 Alfresco Software, Ltd.
*
* 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 { protractor, browser, by, ElementFinder } from 'protractor';
import { APP_ROUTES, BROWSER_WAIT_TIMEOUT } from '../../configs';
import { LoginPage, LogoutPage, BrowsingPage } from '../../pages/pages';
import { CreateOrEditFolderDialog } from '../../components/dialog/create-edit-folder-dialog';
import { LocalStorageUtility } from '../../utilities/local-storage';
import { RepoClient, NodeContentTree } from '../../utilities/repo-client/repo-client';
describe('Create folder', () => {
const username = 'jane.doe';
const password = 'jane.doe';
const folderName1 = 'my-folder1';
const folderName2 = 'my-folder2';
const folderDescription = 'description of my folder';
const duplicateFolderName = 'duplicate-folder-name';
const nameWithSpaces = ' folder name ';
const apis = {
admin: new RepoClient(),
user: new RepoClient(username, password)
};
const loginPage = new LoginPage();
const logoutPage = new LogoutPage();
const personalFilesPage = new BrowsingPage(APP_ROUTES.PERSONAL_FILES);
const createDialog = new CreateOrEditFolderDialog();
const dataTable = personalFilesPage.dataTable;
function openCreateDialog(): any {
return personalFilesPage.sidenav
.openNewMenu()
.then((menu) => {
menu.clickMenuItem('Create folder');
})
.then(() => createDialog.waitForDialogToOpen());
}
beforeAll(done => {
apis.admin.people.createUser(username, password)
.then(() => apis.user.nodes.createFolders([ duplicateFolderName ]))
.then(() => loginPage.load())
.then(() => loginPage.loginWith(username, password))
.then(done);
});
beforeEach(done => {
personalFilesPage.load()
.then(() => dataTable.waitForHeader())
.then(done);
});
afterEach(done => {
browser.$('body').sendKeys(protractor.Key.ESCAPE).then(done);
});
afterAll(done => {
Promise
.all([
apis.user.nodes.deleteNodes([
folderName1,
folderName2,
duplicateFolderName,
nameWithSpaces.trim()
]),
logoutPage.load()
.then(() => LocalStorageUtility.clear())
])
.then(done);
});
it('option is enabled when having enough permissions', () => {
personalFilesPage.sidenav.openNewMenu()
.then((menu) => {
const isEnabled = menu.getItemByLabel('Create folder').getWebElement().isEnabled();
expect(isEnabled).toBe(true, 'Create folder is not enabled');
});
});
it('creates new folder with name', () => {
openCreateDialog()
.then(() => createDialog.enterName(folderName1).clickCreate())
.then(() => createDialog.waitForDialogToClose())
.then(() => dataTable.waitForHeader())
.then(() => {
const isPresent = dataTable.getRowByContainingText(folderName1).isPresent();
expect(isPresent).toBe(true, 'Folder not displayed in list view');
});
});
it('creates new folder with name and description', () => {
openCreateDialog()
.then(() => {
createDialog
.enterName(folderName2)
.enterDescription(folderDescription)
.clickCreate();
})
.then(() => createDialog.waitForDialogToClose())
.then(() => dataTable.waitForHeader())
.then(() => {
const isPresent = dataTable.getRowByContainingText(folderName2).isPresent();
expect(isPresent).toBe(true, 'Folder not displayed in list view');
})
.then(() => {
apis.user.nodes.getNodeDescription(folderName2)
.then((description) => {
expect(description).toEqual(folderDescription, 'Description is not correct');
});
});
});
it('enabled option tooltip', () => {
personalFilesPage.sidenav.openNewMenu()
.then(menu => {
const action = browser.actions().mouseMove(menu.getItemByLabel('Create folder'));
action.perform();
return menu;
})
.then((menu) => {
const tooltip = menu.getItemTooltip('Create folder');
expect(tooltip).toContain('Create new folder');
});
});
it('option is disabled when not enough permissions', () => {
// refactor after implementing Breadcrumb automation component
const breadcrumbRoot: ElementFinder = protractor.element(by.css('.adf-breadcrumb-item[title="User Homes"]'));
browser.actions().mouseMove(breadcrumbRoot).click().perform()
.then(() => personalFilesPage.sidenav.openNewMenu())
.then(menu => {
const isEnabled = menu.getItemByLabel('Create folder').getWebElement().isEnabled();
expect(isEnabled).toBe(false, 'Create folder is not disabled');
});
});
it('disabled option tooltip', () => {
// refactor after implementing Breadcrumb automation component
const breadcrumbRoot: ElementFinder = protractor.element(by.css('.adf-breadcrumb-item[title="User Homes"]'));
browser.actions().mouseMove(breadcrumbRoot).click().perform()
.then(() => personalFilesPage.sidenav.openNewMenu())
.then(menu => {
const action = browser.actions().mouseMove(menu.getItemByLabel('Create folder'));
action.perform()
.then(() => {
const tooltip = menu.getItemTooltip('Create folder');
expect(tooltip).toContain(`You can't create a folder here`);
});
});
});
it('dialog UI elements', () => {
openCreateDialog().then(() => {
const dialogTitle = createDialog.getTitle();
const isFolderNameDisplayed = createDialog.nameInput.getWebElement().isDisplayed();
const isDescriptionDisplayed = createDialog.descriptionTextArea.getWebElement().isDisplayed();
const isCreateEnabled = createDialog.createButton.getWebElement().isEnabled();
const isCancelEnabled = createDialog.cancelButton.getWebElement().isEnabled();
expect(dialogTitle).toBe('Create new folder');
expect(isFolderNameDisplayed).toBe(true, 'Name input is not displayed');
expect(isDescriptionDisplayed).toBe(true, 'Description field is not displayed');
expect(isCreateEnabled).toBe(false, 'Create button is not disabled');
expect(isCancelEnabled).toBe(true, 'Cancel button is not enabled');
});
});
it('with empty folder name', () => {
openCreateDialog()
.then(() => {
createDialog.deleteNameWithBackspace();
})
.then(() => {
const isCreateEnabled = createDialog.createButton.getWebElement().isEnabled();
const validationMessage = createDialog.getValidationMessage();
expect(isCreateEnabled).toBe(false, 'Create button is enabled');
expect(validationMessage).toMatch('Folder name is required');
});
});
it('with folder name ending with a dot "."', () => {
openCreateDialog()
.then(() => createDialog.enterName('folder-name.'))
.then((dialog) => {
const isCreateEnabled = dialog.createButton.getWebElement().isEnabled();
const validationMessage = dialog.getValidationMessage();
expect(isCreateEnabled).toBe(false, 'Create button is not disabled');
expect(validationMessage).toMatch(`Folder name can't end with a period .`);
});
});
it('with folder name containing special characters', () => {
const namesWithSpecialChars = [ 'a*a', 'a"a', 'a<a', 'a>a', `a\\a`, 'a/a', 'a?a', 'a:a', 'a|a' ];
openCreateDialog()
.then(() => {
namesWithSpecialChars.forEach(name => {
createDialog.enterName(name);
const isCreateEnabled = createDialog.createButton.getWebElement().isEnabled();
const validationMessage = createDialog.getValidationMessage();
expect(isCreateEnabled).toBe(false, 'Create button is not disabled');
expect(validationMessage).toContain(`Folder name can't contain these characters`);
});
});
});
it('with folder name containing only spaces', () => {
openCreateDialog()
.then(() => createDialog.enterName(' '))
.then((dialog) => {
const isCreateEnabled = dialog.createButton.getWebElement().isEnabled();
const validationMessage = dialog.getValidationMessage();
expect(isCreateEnabled).toBe(false, 'Create button is not disabled');
expect(validationMessage).toMatch(`Folder name can't contain only spaces`);
});
});
it('cancel folder creation', () => {
openCreateDialog()
.then(() => {
createDialog
.enterName('test')
.enterDescription('test description')
.clickCancel();
})
.then(() => expect(createDialog.component.isPresent()).not.toBe(true, 'dialog is not closed'));
});
it('duplicate folder name', () => {
openCreateDialog()
.then(() => createDialog.enterName(duplicateFolderName).clickCreate())
.then(() => {
personalFilesPage.getSnackBarMessage()
.then(message => {
expect(message).toEqual(`There's already a folder with this name. Try a different name.`);
expect(createDialog.component.isPresent()).toBe(true, 'dialog is not present');
});
});
});
it('trim ending spaces from folder name', () => {
openCreateDialog()
.then(() => createDialog.enterName(nameWithSpaces).clickCreate())
.then(() => createDialog.waitForDialogToClose())
.then(() => dataTable.waitForHeader())
.then(() => {
const isPresent = dataTable.getRowByContainingText(nameWithSpaces.trim()).isPresent();
expect(isPresent).toBe(true, 'Folder not displayed in list view');
});
});
});

View File

@ -0,0 +1,211 @@
/*!
* @license
* Copyright 2017 Alfresco Software, Ltd.
*
* 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 { protractor, element, browser, by, ElementFinder } from 'protractor';
import { LoginPage, LogoutPage, BrowsingPage } from '../../pages/pages';
import { APP_ROUTES, SITE_VISIBILITY, SITE_ROLES } from '../../configs';
import { RepoClient } from '../../utilities/repo-client/repo-client';
import { CreateOrEditFolderDialog } from '../../components/dialog/create-edit-folder-dialog';
import { LocalStorageUtility } from '../../utilities/local-storage';
describe('Edit folder', () => {
const username = 'jane.doe';
const password = 'jane.doe';
const folderName = 'my-folder';
const folderDescription = 'my folder description';
const folderNameToEdit = 'folder-to-be-edited';
const duplicateFolderName = 'duplicate-folder-name';
const folderNameEdited = 'edited-folder';
const folderDescriptionEdited = 'my folder description edited';
const siteName = 'site-private';
const apis = {
admin: new RepoClient(),
user: new RepoClient(username, password)
};
const loginPage = new LoginPage();
const logoutPage = new LogoutPage();
const personalFilesPage = new BrowsingPage();
const editDialog = new CreateOrEditFolderDialog();
const dataTable = personalFilesPage.dataTable;
const editButton = personalFilesPage.toolbar.actions.getButtonByTitleAttribute('Edit');
beforeAll(done => {
Promise
.all([
apis.admin.people.createUser(username, password),
apis.admin.sites.createSite(siteName, SITE_VISIBILITY.PRIVATE)
.then(() => apis.admin.nodes.createFolders([ folderName ], `Sites/${siteName}/documentLibrary`))
])
.then(() => apis.admin.sites.addSiteMember(siteName, username, SITE_ROLES.SITE_CONSUMER))
.then(() => Promise.all([
apis.user.nodes.createNodeWithProperties( folderName, '', folderDescription ),
apis.user.nodes.createFolders([ folderNameToEdit, duplicateFolderName ]),
loginPage.load()
]))
.then(() => loginPage.loginWith(username, password))
.then(done);
});
beforeEach(done => {
personalFilesPage.load()
.then(() => dataTable.waitForHeader())
.then(done);
});
afterEach(done => {
browser.$('body').sendKeys(protractor.Key.ESCAPE).then(done);
});
afterAll(done => {
Promise
.all([
apis.admin.sites.deleteSite(siteName, true),
apis.user.nodes.deleteNodes([ folderName, folderNameEdited, duplicateFolderName ]),
logoutPage.load()
.then(() => LocalStorageUtility.clear())
])
.then(done);
});
it('button is enabled when having permissions', () => {
dataTable.clickOnRowByContainingText(folderName)
.then(() => {
expect(editButton.isEnabled()).toBe(true);
});
});
it('dialog UI defaults', () => {
dataTable.clickOnRowByContainingText(folderName)
.then(() => editButton.click())
.then(() => {
expect(editDialog.getTitle()).toBe('Edit folder');
expect(editDialog.nameInput.getWebElement().getAttribute('value')).toBe(folderName);
expect(editDialog.descriptionTextArea.getWebElement().getAttribute('value')).toBe(folderDescription);
expect(editDialog.updateButton.getWebElement().isEnabled()).toBe(true, 'upload button is not enabled');
expect(editDialog.cancelButton.getWebElement().isEnabled()).toBe(true, 'cancel button is not enabled');
});
});
it('folder properties are modified when pressing OK', () => {
dataTable.clickOnRowByContainingText(folderNameToEdit)
.then(() => editButton.click())
.then(() => {
editDialog
.enterName(folderNameEdited)
.enterDescription(folderDescriptionEdited)
.clickUpdate();
})
.then(() => editDialog.waitForDialogToClose())
.then(() => dataTable.waitForHeader())
.then(() => {
const isPresent = dataTable.getRowByContainingText(folderNameEdited).isPresent();
expect(isPresent).toBe(true, 'Folder not displayed in list view');
})
.then(() => {
apis.user.nodes.getNodeDescription(folderNameEdited)
.then((description) => {
expect(description).toEqual(folderDescriptionEdited);
});
});
});
it('button is not displayed when not enough permissions', () => {
const fileLibrariesPage = new BrowsingPage();
fileLibrariesPage.sidenav.navigateToLinkByLabel('File Libraries')
.then(() => fileLibrariesPage.dataTable.doubleClickOnRowByContainingText(siteName))
.then(() => fileLibrariesPage.dataTable.clickOnRowByContainingText(folderName))
.then(() => {
expect(editButton.isPresent()).not.toBe(true, 'edit button is displayed');
});
});
it('with empty folder name', () => {
dataTable.clickOnRowByContainingText(folderName)
.then(() => editButton.click())
.then(() => {
editDialog.deleteNameWithBackspace();
})
.then(() => {
expect(editDialog.updateButton.getWebElement().isEnabled()).toBe(false, 'upload button is not enabled');
expect(editDialog.getValidationMessage()).toMatch('Folder name is required');
});
});
it('with name with special characters', () => {
const namesWithSpecialChars = [ 'a*a', 'a"a', 'a<a', 'a>a', `a\\a`, 'a/a', 'a?a', 'a:a', 'a|a' ];
dataTable.clickOnRowByContainingText(folderName)
.then(() => editButton.click())
.then(() => {
namesWithSpecialChars.forEach(name => {
editDialog.enterName(name);
expect(editDialog.updateButton.getWebElement().isEnabled()).toBe(false, 'upload button is not disabled');
expect(editDialog.getValidationMessage()).toContain(`Folder name can't contain these characters`);
});
});
});
it('with name ending with a dot', () => {
dataTable.clickOnRowByContainingText(folderName)
.then(() => editButton.click())
.then(() => editDialog.nameInput.sendKeys('.'))
.then(() => {
expect(editDialog.updateButton.getWebElement().isEnabled()).toBe(false, 'upload button is not enabled');
expect(editDialog.getValidationMessage()).toMatch(`Folder name can't end with a period .`);
});
});
it('Cancel button', () => {
dataTable.clickOnRowByContainingText(folderName)
.then(() => editButton.click())
.then(() => editDialog.clickCancel())
.then(() => expect(editDialog.component.isPresent()).not.toBe(true, 'dialog is not closed'));
});
it('with duplicate folder name', () => {
dataTable.clickOnRowByContainingText(folderName)
.then(() => editButton.click())
.then(() => editDialog.enterName(duplicateFolderName).clickUpdate())
.then(() => {
personalFilesPage.getSnackBarMessage()
.then(message => {
expect(message).toEqual(`There's already a folder with this name. Try a different name.`);
expect(editDialog.component.isPresent()).toBe(true, 'dialog is not present');
});
});
});
it('trim ending spaces', () => {
dataTable.clickOnRowByContainingText(folderName)
.then(() => editButton.click())
.then(() => editDialog.nameInput.sendKeys(' '))
.then(() => editDialog.clickUpdate())
.then(() => editDialog.waitForDialogToClose())
.then(() => {
expect(personalFilesPage.snackBar.isPresent()).not.toBe(true, 'notification appears');
expect(dataTable.getRowByContainingText(folderName).isPresent()).toBe(true, 'Folder not displayed in list view');
});
});
});

View File

@ -0,0 +1,126 @@
/*!
* @license
* Copyright 2017 Alfresco Software, Ltd.
*
* 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 { browser } from 'protractor';
import { SIDEBAR_LABELS } from '../../configs';
import { LoginPage, LogoutPage, BrowsingPage } from '../../pages/pages';
import { LocalStorageUtility } from '../../utilities/local-storage';
describe('Page titles', () => {
const loginPage = new LoginPage();
const logoutPage = new LogoutPage();
const page = new BrowsingPage();
xit('');
describe('on Login / Logout pages', () => {
it('on Login page', () => {
loginPage.load()
.then(() => {
expect(browser.getTitle()).toContain('Sign in');
});
});
it('after logout', () => {
loginPage.load()
.then(() => loginPage.loginWithAdmin())
.then(() => page.signOut())
.then(() => {
expect(browser.getTitle()).toContain('Sign in');
});
});
it('when pressing Back after Logout', () => {
loginPage.load()
.then(() => loginPage.loginWithAdmin())
.then(() => page.signOut())
.then(() => browser.driver.navigate().back())
.then(() => {
expect(browser.getTitle()).toContain('Sign in');
});
});
});
describe('on list views', () => {
beforeAll(done => {
loginPage.load()
.then(() => loginPage.loginWithAdmin())
.then(done);
});
afterAll(done => {
logoutPage.load()
.then(() => LocalStorageUtility.clear())
.then(done);
});
it('Personal Files page', () => {
const label = SIDEBAR_LABELS.PERSONAL_FILES;
page.sidenav.navigateToLinkByLabel(label)
.then(() => {
expect(browser.getTitle()).toContain(label);
});
});
it('File Libraries page', () => {
const label = SIDEBAR_LABELS.FILE_LIBRARIES;
page.sidenav.navigateToLinkByLabel(label)
.then(() => {
expect(browser.getTitle()).toContain(label);
});
});
it('Shared Files page', () => {
const label = SIDEBAR_LABELS.SHARED_FILES;
page.sidenav.navigateToLinkByLabel(label)
.then(() => {
expect(browser.getTitle()).toContain(label);
});
});
it('Recent Files page', () => {
const label = SIDEBAR_LABELS.RECENT_FILES;
page.sidenav.navigateToLinkByLabel(label)
.then(() => {
expect(browser.getTitle()).toContain(label);
});
});
it('Favorites page', () => {
const label = SIDEBAR_LABELS.FAVORITES;
page.sidenav.navigateToLinkByLabel(label)
.then(() => {
expect(browser.getTitle()).toContain(label);
});
});
it('Trash page', () => {
const label = SIDEBAR_LABELS.TRASH;
page.sidenav.navigateToLinkByLabel(label)
.then(() => {
expect(browser.getTitle()).toContain(label);
});
});
});
});

View File

@ -0,0 +1,178 @@
/*!
* @license
* Copyright 2017 Alfresco Software, Ltd.
*
* 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 { browser } from 'protractor';
import { APP_ROUTES } from '../../configs';
import { LoginPage, LogoutPage, BrowsingPage } from '../../pages/pages';
import { LocalStorageUtility } from '../../utilities/local-storage';
import { RepoClient } from '../../utilities/repo-client/repo-client';
describe('Login', () => {
const peopleApi = new RepoClient().people;
const loginPage = new LoginPage();
const logoutPage = new LogoutPage();
const testUser = {
username: 'test.user@alfness',
password: 'test.user'
};
const russianUser = {
username: 'пользователь',
password: '密碼中國'
};
const johnDoe = {
username: 'john.doe',
password: 'john.doe',
firstName: 'John',
lastName: 'Doe'
};
beforeAll(done => {
Promise
.all([
peopleApi.createUser(testUser.username, testUser.password),
peopleApi.createUser(russianUser.username, russianUser.password),
peopleApi.createUser(johnDoe.username, johnDoe.password, {
firstName: johnDoe.firstName,
lastName: johnDoe.lastName
})
])
.then(done);
});
beforeEach(done => {
loginPage.load().then(done);
});
afterEach(done => {
logoutPage.load()
.then(() => LocalStorageUtility.clear())
.then(done);
});
xit('');
describe('with valid credentials', () => {
it('navigate to "Personal Files"', () => {
const { username, password } = johnDoe;
loginPage
.loginWith(username, password)
.then(() => {
expect(browser.getCurrentUrl()).toContain(APP_ROUTES.PERSONAL_FILES);
});
});
it(`displays user's name in header`, () => {
const { userInfo } = new BrowsingPage(APP_ROUTES.PERSONAL_FILES).header;
const { username, password, firstName, lastName } = johnDoe;
loginPage
.loginWith(username, password)
.then(() => {
expect(userInfo.name).toEqual(`${firstName} ${lastName}`);
});
});
it(`logs in with user having username containing "@"`, () => {
const { username, password } = testUser;
loginPage
.loginWith(username, password)
.then(() => {
expect(browser.getCurrentUrl()).toContain(APP_ROUTES.PERSONAL_FILES);
});
});
it('logs in with user with non-latin characters', () => {
const { username, password } = russianUser;
loginPage
.loginWith(username, password)
.then(() => {
expect(browser.getCurrentUrl()).toContain(APP_ROUTES.PERSONAL_FILES);
});
});
it('redirects to Home Page when navigating to the Login page while already logged in', () => {
const { username, password } = johnDoe;
loginPage
.loginWith(username, password)
.then(() => browser.get(APP_ROUTES.LOGIN)
.then(() => {
expect(browser.getCurrentUrl()).toContain(APP_ROUTES.PERSONAL_FILES);
})
);
});
it('redirects to Personal Files when pressing browser Back while already logged in ', () => {
const { username, password } = johnDoe;
loginPage
.loginWith(username, password)
.then(() => browser.driver.navigate().back())
.then(() => {
expect(browser.getCurrentUrl()).toContain(APP_ROUTES.PERSONAL_FILES);
});
});
});
describe('with invalid credentials', () => {
const { login: loginComponent } = loginPage;
const { submitButton, errorMessage } = loginComponent;
it('disabled submit button when no credentials are entered', () => {
expect(submitButton.isEnabled()).toBe(false);
});
it('disabled submit button when password is empty', () => {
loginComponent.enterUsername('any-username');
expect(submitButton.isEnabled()).toBe(false);
});
it('disabled submit button when username is empty', () => {
loginPage.login.enterPassword('any-password');
expect(submitButton.isEnabled()).toBe(false);
});
it('shows error when entering nonexistent user', () => {
loginPage
.loginWith('nonexistent-user', 'any-password')
.then(() => {
expect(browser.getCurrentUrl()).toContain(APP_ROUTES.LOGIN);
expect(errorMessage.isDisplayed()).toBe(true);
});
});
it('shows error when entering invalid password', () => {
const { username } = johnDoe;
loginPage
.loginWith(username, 'incorrect-password')
.then(() => {
expect(browser.getCurrentUrl()).toContain(APP_ROUTES.LOGIN);
expect(errorMessage.isDisplayed()).toBe(true);
});
});
});
});

View File

@ -0,0 +1,76 @@
/*!
* @license
* Copyright 2017 Alfresco Software, Ltd.
*
* 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 { browser } from 'protractor';
import { APP_ROUTES, BROWSER_WAIT_TIMEOUT } from '../../configs';
import { LoginPage, LogoutPage, BrowsingPage } from '../../pages/pages';
import { LocalStorageUtility } from '../../utilities/local-storage';
import { RepoClient } from '../../utilities/repo-client/repo-client';
describe('Logout', () => {
const page = new BrowsingPage();
const loginPage = new LoginPage();
const logoutPage = new LogoutPage();
const peopleApi = new RepoClient().people;
const johnDoe = {
username: 'john.doe',
password: 'john.doe'
};
beforeAll((done) => {
peopleApi
.createUser(johnDoe.username, johnDoe.password)
.then(done);
});
beforeEach((done) => {
loginPage.load()
.then(() => loginPage.loginWith(johnDoe.username, johnDoe.password))
.then(done);
});
afterEach((done) => {
logoutPage.load()
.then(() => LocalStorageUtility.clear())
.then(done);
});
it('redirects to Login page, on sign out', () => {
page.signOut()
.then(() => {
expect(browser.getCurrentUrl()).toContain(APP_ROUTES.LOGIN);
});
});
xit('redirects to Login page when logging out by URL', () => {
browser.get(APP_ROUTES.LOGOUT)
.then(() => {
expect(browser.getCurrentUrl()).toContain(APP_ROUTES.LOGIN);
});
});
it('redirects to Login page when pressing browser Back after logout', () => {
page.signOut()
.then(() => browser.driver.navigate().back())
.then(() => {
expect(browser.getCurrentUrl()).toContain(APP_ROUTES.LOGIN);
});
});
});

View File

@ -0,0 +1,180 @@
/*!
* @license
* Copyright 2017 Alfresco Software, Ltd.
*
* 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 { browser } from 'protractor';
import { APP_ROUTES } from '../../configs';
import { LoginPage, LogoutPage, BrowsingPage } from '../../pages/pages';
import { LocalStorageUtility } from '../../utilities/local-storage';
import { RepoClient, NodeContentTree } from '../../utilities/repo-client/repo-client';
describe('Personal Files', () => {
const username = 'jane.doe';
const password = 'jane.doe';
const apis = {
admin: new RepoClient(),
user: new RepoClient(username, password)
};
const loginPage = new LoginPage();
const logoutPage = new LogoutPage();
const personalFilesPage = new BrowsingPage(APP_ROUTES.PERSONAL_FILES);
const dataTable = personalFilesPage.dataTable;
const adminContent: NodeContentTree = {
name: 'admin-folder'
};
const userContent: NodeContentTree = {
name: 'user-folder',
files: [ 'user-file.txt' ]
};
beforeAll(done => {
Promise
.all([
apis.admin.people.createUser(username, password),
apis.admin.nodes.createContent(adminContent)
])
.then(() => apis.user.nodes.createContent(userContent))
.then(done);
});
afterAll(done => {
Promise
.all([
apis.admin.nodes.deleteNodes([ adminContent.name ]),
apis.user.nodes.deleteNodes([ userContent.name ])
])
.then(done);
});
xit('');
describe(`Admin user's personal files`, () => {
beforeAll(done => {
loginPage.load()
.then(() => loginPage.loginWithAdmin())
.then(done);
});
beforeEach(done => {
personalFilesPage.load()
.then(() => dataTable.waitForHeader())
.then(done);
});
afterAll(done => {
logoutPage.load()
.then(() => LocalStorageUtility.clear())
.then(done);
});
it('has "Data Dictionary" folder', () => {
expect(dataTable.getRowByContainingText('Data Dictionary').isPresent()).toBe(true);
});
it('has created content', () => {
expect(dataTable.getRowByContainingText('admin-folder').isPresent()).toBe(true);
});
});
describe(`Regular user's personal files`, () => {
beforeAll(done => {
loginPage.load()
.then(() => loginPage.loginWith(username, password))
.then(done);
});
beforeEach(done => {
personalFilesPage.load()
.then(() => dataTable.waitForHeader())
.then(done);
});
afterAll(done => {
logoutPage.load()
.then(() => LocalStorageUtility.clear())
.then(done);
});
it('has the right column count', () => {
expect(dataTable.getColumnHeaders().count()).toBe(5);
});
it('has the right columns', () => {
const labels = [ 'Name', 'Size', 'Modified', 'Modified by' ];
const elements = labels.map(label => dataTable.getColumnHeaderByLabel(label));
elements.forEach((element, index) => {
expect(element.isPresent()).toBe(true, `"${labels[index]}" is missing`);
});
});
it('has default sorted column', () => {
expect(dataTable.getSortedColumnHeader().getText()).toBe('Modified');
});
it('has user created content', () => {
expect(dataTable.getRowByContainingText('user-folder').isPresent())
.toBe(true);
});
it('navigates to folder', () => {
const getNodeIdPromise = apis.user.nodes
.getNodeByPath('/user-folder')
.then(response => response.data.entry.id);
const navigatePromise = dataTable
.doubleClickOnRowByContainingText('user-folder')
.then(() => dataTable.waitForHeader());
Promise
.all([
getNodeIdPromise,
navigatePromise
])
.then(([ nodeId ]) => {
expect(browser.getCurrentUrl())
.toContain(nodeId, 'Node ID is not in the URL');
expect(dataTable.getRowByContainingText('user-file.txt').isPresent())
.toBe(true, '"user-file.txt" is missing');
});
});
// Some tests regarding selection, breadcrumb and toolbar
// probably they can be move to a different suite
describe('Item selection', () => {
it('has toolbar when selected', done => {
const { actions } = personalFilesPage.toolbar;
dataTable
.clickOnRowByContainingText('user-folder')
.then(() => {
expect(actions.isEmpty()).toBe(false, 'Toolbar to be present');
})
.then(() => actions.openMoreMenu())
.then(menu => {
expect(menu.items.count()).toBeGreaterThan(0, 'More actions has items');
})
.then(done);
});
});
});
});

View File

@ -0,0 +1,124 @@
/*!
* @license
* Copyright 2017 Alfresco Software, Ltd.
*
* 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 { browser } from 'protractor';
import { APP_ROUTES, SIDEBAR_LABELS } from '../../configs';
import { LoginPage, LogoutPage, BrowsingPage } from '../../pages/pages';
import { LocalStorageUtility } from '../../utilities/local-storage';
describe('Side navigation', () => {
const loginPage = new LoginPage();
const logoutPage = new LogoutPage();
const page = new BrowsingPage();
beforeAll(done => {
loginPage.load()
.then(() => loginPage.loginWithAdmin())
.then(done);
});
beforeEach(done => {
page.dataTable.wait().then(done);
});
afterAll(done => {
logoutPage.load()
.then(() => LocalStorageUtility.clear())
.then(done);
});
it('has "Personal Files" as default', () => {
expect(browser.getCurrentUrl())
.toContain(APP_ROUTES.PERSONAL_FILES);
expect(page.sidenav.isActiveByLabel('Personal Files'))
.toBe(true, 'Active link');
});
it('navigates to "File Libraries"', () => {
const { sidenav, dataTable } = page;
sidenav.navigateToLinkByLabel(SIDEBAR_LABELS.FILE_LIBRARIES)
.then(() => dataTable.wait())
.then(() => browser.getCurrentUrl())
.then(url => {
expect(url).toContain(APP_ROUTES.FILE_LIBRARIES);
expect(sidenav.isActiveByLabel(SIDEBAR_LABELS.FILE_LIBRARIES)).toBe(true);
});
});
it('navigates to "Personal Files"', () => {
const { sidenav, dataTable } = page;
sidenav.navigateToLinkByLabel(SIDEBAR_LABELS.PERSONAL_FILES)
.then(() => dataTable.wait())
.then(() => browser.getCurrentUrl())
.then(url => {
expect(url).toContain(APP_ROUTES.PERSONAL_FILES);
expect(sidenav.isActiveByLabel(SIDEBAR_LABELS.PERSONAL_FILES)).toBe(true);
});
});
it('navigates to "Shared Files"', () => {
const { sidenav, dataTable } = page;
sidenav.navigateToLinkByLabel(SIDEBAR_LABELS.SHARED_FILES)
.then(() => dataTable.wait())
.then(() => browser.getCurrentUrl())
.then(url => {
expect(url).toContain(APP_ROUTES.SHARED_FILES);
expect(sidenav.isActiveByLabel(SIDEBAR_LABELS.SHARED_FILES)).toBe(true);
});
});
it('navigates to "Recent Files"', () => {
const { sidenav, dataTable } = page;
sidenav.navigateToLinkByLabel(SIDEBAR_LABELS.RECENT_FILES)
.then(() => dataTable.wait())
.then(() => browser.getCurrentUrl())
.then(url => {
expect(url).toContain(APP_ROUTES.RECENT_FILES);
expect(sidenav.isActiveByLabel(SIDEBAR_LABELS.RECENT_FILES)).toBe(true);
});
});
it('navigates to "Favorites"', () => {
const { sidenav, dataTable } = page;
sidenav.navigateToLinkByLabel(SIDEBAR_LABELS.FAVORITES)
.then(() => dataTable.wait())
.then(() => browser.getCurrentUrl())
.then(url => {
expect(url).toContain(APP_ROUTES.FAVORITES);
expect(sidenav.isActiveByLabel(SIDEBAR_LABELS.FAVORITES)).toBe(true);
});
});
it('navigates to "Trash"', () => {
const { sidenav, dataTable } = page;
sidenav.navigateToLinkByLabel(SIDEBAR_LABELS.TRASH)
.then(() => dataTable.wait())
.then(() => browser.getCurrentUrl())
.then(url => {
expect(url).toContain(APP_ROUTES.TRASHCAN);
expect(sidenav.isActiveByLabel(SIDEBAR_LABELS.TRASH)).toBe(true);
});
});
});

View File

@ -0,0 +1,169 @@
/*!
* @license
* Copyright 2017 Alfresco Software, Ltd.
*
* 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 { browser } from 'protractor';
import { APP_ROUTES } from '../../configs';
import { LoginPage, LogoutPage, BrowsingPage } from '../../pages/pages';
import { LocalStorageUtility } from '../../utilities/local-storage';
import { RepoClient, NodeContentTree } from '../../utilities/repo-client/repo-client';
describe('Pagination', () => {
const username = 'jane.doe';
const password = 'jane.doe';
const apis = {
admin: new RepoClient(),
user: new RepoClient(username, password)
};
const loginPage = new LoginPage();
const logoutPage = new LogoutPage();
beforeAll(done => {
apis.admin.people
.createUser(username, password)
.then(done);
});
xit('');
describe(`on Personal Files`, () => {
const personalFilesPage = new BrowsingPage(APP_ROUTES.PERSONAL_FILES);
const { dataTable, pagination } = personalFilesPage;
// Generate files
const content: NodeContentTree = {
name: 'user-folder',
files: Array(101)
.fill('file')
.map((name, index): string => `${name}-${index + 1}.txt`)
};
const { nodes: nodesApi } = apis.user;
beforeAll(done => {
nodesApi.createContent(content)
.then(() => loginPage.load())
.then(() => loginPage.loginWith(username, password))
.then(done);
});
beforeEach(done => {
personalFilesPage
.load()
.then(() => dataTable.waitForHeader())
.then(() => dataTable.doubleClickOnRowByContainingText(content.name))
.then(() => dataTable.sortByColumn('Name'))
.then(done);
});
afterAll(done => {
logoutPage
.load()
.then(() => LocalStorageUtility.clear())
.then(() => nodesApi.deleteNodes([ content.name ]))
.then(done);
});
it('has default details', () => {
expect(pagination.range.getText()).toContain('1-25 of 101', 'Range');
expect(pagination.maxItems.getText()).toContain('25', 'Items per page');
expect(pagination.currentPage.getText()).toContain('Page 1', 'Current page');
expect(pagination.totalPages.getText()).toContain('of 5', 'Total pages');
expect(pagination.previousButton.isEnabled()).toBe(false, 'Previous button');
expect(pagination.nextButton.isEnabled()).toBe(true, 'Next button');
});
it('has page sizes', () => {
pagination.openMaxItemsMenu()
.then(menu => {
const [ first, second, third ] = [1, 2, 3]
.map(nth => menu.getNthItem(nth).getText());
expect(first).toBe('25', 'Items per page');
expect(second).toBe('50', 'Items per page');
expect(third).toBe('100', 'Items per page');
});
});
it('changes the page size', () => {
pagination.openMaxItemsMenu()
.then(menu => menu.clickMenuItem('50'))
.then(() => dataTable.waitForHeader())
.then(() => {
expect(pagination.maxItems.getText()).toContain('50', 'Items per page');
expect(pagination.totalPages.getText()).toContain('of 3', 'Total pages');
});
});
it('has page items', () => {
pagination.openCurrentPageMenu()
.then(menu => {
expect(menu.items.count()).toBe(5);
});
});
it('changes the current page from menu', () => {
pagination.openCurrentPageMenu()
.then(menu => menu.clicktNthItem(3))
.then(() => dataTable.waitForHeader())
.then(() => {
expect(pagination.range.getText()).toContain('51-75 of 101', 'Range');
expect(pagination.currentPage.getText()).toContain('Page 3', 'Current page');
expect(pagination.previousButton.isEnabled()).toBe(true, 'Previous button');
expect(pagination.nextButton.isEnabled()).toBe(true, 'Next button');
expect(dataTable.getRowByContainingText('file-60.txt').isPresent())
.toBe(true, 'File not found on page');
});
});
it('navigates to next page', () => {
pagination.nextButton.click()
.then(() => dataTable.waitForHeader())
.then(() => {
expect(pagination.range.getText()).toContain('26-50 of 101', 'Range');
expect(dataTable.getRowByContainingText('file-30.txt').isPresent())
.toBe(true, 'File not found on page');
});
});
it('navigates to previous page', () => {
pagination.openCurrentPageMenu()
.then(menu => menu.clicktNthItem(2))
.then(() => dataTable.waitForHeader())
.then(() => pagination.previousButton.click())
.then(() => dataTable.waitForHeader())
.then(() => {
expect(pagination.range.getText()).toContain('1-25 of 101', 'Range');
expect(dataTable.getRowByContainingText('file-12.txt').isPresent())
.toBe(true, 'File not found on page');
});
});
it('has one item on the last page', () => {
pagination.openCurrentPageMenu()
.then(menu => menu.clicktNthItem(5))
.then(() => dataTable.waitForHeader())
.then(() => {
expect(dataTable.countRows()).toBe(1, 'Single item on the last page');
expect(pagination.currentPage.getText()).toContain('Page 5', 'Last page');
expect(pagination.nextButton.isEnabled()).toBe(false, 'Next button is not enabled');
});
});
});
});

View File

@ -0,0 +1,34 @@
/*!
* @license
* Copyright 2017 Alfresco Software, Ltd.
*
* 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 { browser } from 'protractor';
declare var window;
export class LocalStorageUtility {
static clear(): Promise<any> {
return browser.executeScript(() => {
return window.localStorage.clear();
});
}
static getTicket(): Promise<any> {
return browser.executeScript(() => {
return window.localStorage.getItem('ticket-ECM');
});
}
}

View File

@ -0,0 +1,30 @@
/*!
* @license
* Copyright 2017 Alfresco Software, Ltd.
*
* 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 const NODE_TYPE_FILE = 'cm:content';
export const NODE_TYPE_FOLDER = 'cm:folder';
export const NODE_TITLE = 'cm:title';
export const NODE_DESCRIPTION = 'cm:description';
export class NodeBodyCreate {
constructor(
public name: string,
public nodeType: string,
public relativePath: string = '/',
public properties?: any[]
) {}
}

View File

@ -0,0 +1,77 @@
/*!
* @license
* Copyright 2017 Alfresco Software, Ltd.
*
* 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 { NodeBodyCreate, NODE_TYPE_FILE, NODE_TYPE_FOLDER, NODE_TITLE, NODE_DESCRIPTION } from './node-body-create';
export interface NodeContentTree {
name?: string;
files?: string[];
folders?: (string|NodeContentTree)[];
title?: string;
description?: string;
}
export function flattenNodeContentTree(content: NodeContentTree, relativePath: string = '/'): NodeBodyCreate[] {
const { name, files, folders, title, description } = content;
let data: NodeBodyCreate[] = [];
let properties: any;
properties = {
[NODE_TITLE]: title,
[NODE_DESCRIPTION]: description
};
if (name) {
data = data.concat([{
nodeType: NODE_TYPE_FOLDER,
name,
relativePath,
properties
}]);
relativePath = (relativePath === '/')
? `/${name}`
: `${relativePath}/${name}`;
}
if (folders) {
const foldersData: NodeBodyCreate[] = folders
.map((folder: (string|NodeContentTree)): NodeBodyCreate[] => {
const folderData: NodeContentTree = (typeof folder === 'string')
? { name: folder }
: folder;
return flattenNodeContentTree(folderData, relativePath);
})
.reduce((nodesData: NodeBodyCreate[], folderData: NodeBodyCreate[]) => nodesData.concat(folderData), []);
data = data.concat(foldersData);
}
if (files) {
const filesData: NodeBodyCreate[] = files
.map((filename: string): NodeBodyCreate => ({
nodeType: NODE_TYPE_FILE,
name: filename,
relativePath
}));
data = data.concat(filesData);
}
return data;
}

View File

@ -0,0 +1,96 @@
/*!
* @license
* Copyright 2017 Alfresco Software, Ltd.
*
* 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 { RepoApi } from '../repo-api';
import { NodeBodyCreate, NODE_TYPE_FILE, NODE_TYPE_FOLDER } from './node-body-create';
import { NodeContentTree, flattenNodeContentTree } from './node-content-tree';
export class NodesApi extends RepoApi {
// nodes
getNodeByPath(relativePath: string = '/'): Promise<any> {
return this
.get(`/nodes/-my-`, { parameters: { relativePath } })
.catch(this.handleError);
}
getNodeById(id: string): Promise<any> {
return this
.get(`/nodes/${id}`)
.catch(this.handleError);
}
deleteNodeById(id: string): Promise<any> {
return this
.delete(`/nodes/${id}`)
.catch(this.handleError);
}
deleteNodeByPath(path: string): Promise<any> {
return this
.getNodeByPath(path)
.then((response: any): string => response.data.entry.id)
.then((id: string): any => this.deleteNodeById(id))
.catch(this.handleError);
}
getNodeDescription(name: string): Promise<string> {
let description = 'cm:description';
return this
.getNodeByPath(name)
.then((response: any): string => response.data.entry.properties[description])
.catch(this.handleError);
}
deleteNodes(names: string[], relativePath: string = ''): Promise<any> {
const deletions = names
.map((name: string): any => {
return this.deleteNodeByPath(`${relativePath}/${name}`);
});
return Promise.all(deletions);
}
// children
getNodeChildren(nodeId: string): Promise<any> {
return this
.get(`/nodes/${nodeId}/children`)
.catch(this.handleError);
}
createChildren(data: NodeBodyCreate[]): Promise<any> {
return this
.post(`/nodes/-my-/children`, { data })
.catch(this.handleError);
}
createContent(content: NodeContentTree, relativePath: string = '/'): Promise<any> {
return this.createChildren(flattenNodeContentTree(content, relativePath));
}
createNodeWithProperties(name: string, title?: string, description?: string, relativePath: string = '/'): Promise<any> {
return this.createContent({ name, title, description }, relativePath);
}
createFolders(names: string[], relativePath: string = '/'): Promise<any> {
return this.createContent({ folders: names }, relativePath);
}
createFiles(names: string[], relativePath: string = '/'): Promise<any> {
return this.createContent({ files: names }, relativePath);
}
}

View File

@ -0,0 +1,35 @@
/*!
* @license
* Copyright 2017 Alfresco Software, Ltd.
*
* 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 class Person {
id?: string;
password?: string;
firstName?: string;
lastName?: string;
email?: string;
properties?: any;
constructor(username: string, password: string, details: Person) {
this.id = username;
this.password = password || username;
this.firstName = username;
this.lastName = username;
this.email = `${username}@alfresco.com`;
Object.assign(this, details);
}
}

View File

@ -0,0 +1,52 @@
/*!
* @license
* Copyright 2017 Alfresco Software, Ltd.
*
* 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 { RepoApi } from '../repo-api';
import { Person } from './people-api-models';
export class PeopleApi extends RepoApi {
getUser(username: string) {
return this
.get(`/people/${username}`)
.catch(this.handleError);
}
updateUser(username: string, details?: Person): Promise<any> {
if (details.id) {
delete details.id;
}
return this
.put(`/people/${username}`, { data: details })
.catch(this.handleError);
}
createUser(username: string, password: string, details?: Person): Promise<any> {
const person: Person = new Person(username, password, details);
const onSuccess = (response) => response;
const onError = (response) => {
return (response.statusCode === 409)
? Promise.resolve(this.updateUser(username, person))
: Promise.reject(response);
};
return this
.post(`/people`, { data: person })
.then(onSuccess, onError)
.catch(this.handleError);
}
}

View File

@ -0,0 +1,63 @@
/*!
* @license
* Copyright 2017 Alfresco Software, Ltd.
*
* 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 { RestClient, RestClientArgs, RestClientResponse } from '../../rest-client/rest-client';
import { RepoClientAuth, RepoClientConfig } from '../repo-client-models';
export abstract class RepoApi {
private client: RestClient;
private defaults: RepoClientConfig = new RepoClientConfig();
constructor(
auth: RepoClientAuth = new RepoClientAuth(),
private config?: RepoClientConfig
) {
const { username, password } = auth;
this.client = new RestClient(username, password);
}
private createEndpointUri(endpoint: string): string {
const { defaults, config } = this;
const { host, tenant } = Object.assign(defaults, config);
return `${host}/alfresco/api/${tenant}/public/alfresco/versions/1${endpoint}`;
}
protected handleError(response: RestClientResponse) {
const { request: { method, path, data }, data: error } = response;
console.log(`ERROR on ${method}\n${path}\n${data}`);
console.log(error);
}
protected get(endpoint: string, args: RestClientArgs = {}) {
return this.client.get(this.createEndpointUri(endpoint), args);
}
protected post(endpoint: string, args: RestClientArgs = {}) {
return this.client.post(this.createEndpointUri(endpoint), args);
}
protected put(endpoint: string, args: RestClientArgs = {}) {
return this.client.put(this.createEndpointUri(endpoint), args);
}
protected delete(endpoint: string, args: RestClientArgs = {}) {
return this.client.delete(this.createEndpointUri(endpoint), args);
}
}

View File

@ -0,0 +1,34 @@
/*!
* @license
* Copyright 2017 Alfresco Software, Ltd.
*
* 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 { SITE_VISIBILITY } from '../../../../configs';
export class Site {
title?: string;
visibility?: string = SITE_VISIBILITY.PUBLIC;
id?: string;
description?: string;
constructor(title: string, visibility: string, details: Site) {
this.title = title;
this.visibility = visibility;
this.id = title;
this.description = `${title} description`;
Object.assign(this, details);
}
}

View File

@ -0,0 +1,84 @@
/*!
* @license
* Copyright 2017 Alfresco Software, Ltd.
*
* 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 { RepoApi } from '../repo-api';
import { Site } from './sites-api-models';
export class SitesApi extends RepoApi {
getSite(id: string) {
return this
.get(`/sites/${id}`)
.catch(this.handleError);
}
updateSite(id: string, details?: Site): Promise<any> {
if (details.id) {
delete details.id;
}
return this
.put(`/sites/${id}`, { data: details })
.catch(this.handleError);
}
createSite(title: string, visibility: string, details?: Site): Promise<any> {
const site: Site = new Site(title, visibility, details);
const onSuccess = (response) => response;
const onError = (response) => {
return (response.statusCode === 409)
? Promise.resolve(this.updateSite(site.id, site))
: Promise.reject(response);
};
return this
.post(`/sites`, { data: site })
.then(onSuccess, onError)
.catch(this.handleError);
}
deleteSite(id: string, permanent: boolean = true): Promise<any> {
return this
.delete(`/sites/${id}?permanent=${permanent}`)
.catch(this.handleError);
}
updateSiteMember(siteId: string, userId: string, role: string): Promise<any> {
return this
.put(`/sites/${siteId}/members/${userId}`, { data: { role } })
.catch(this.handleError);
}
addSiteMember(siteId: string, userId: string, role: string): Promise<any> {
const onSuccess = (response) => response;
const onError = (response) => {
return (response.statusCode === 409)
? Promise.resolve(this.updateSiteMember(siteId, userId, role))
: Promise.reject(response);
};
return this
.post(`/sites/${siteId}/members`, { data: { role, id: userId } })
.then(onSuccess, onError)
.catch(this.handleError);
}
deleteSiteMember(siteId: string, userId: string): Promise<any> {
return this
.delete(`/sites/${siteId}/members/${userId}`)
.catch(this.handleError);
}
}

View File

@ -0,0 +1,38 @@
/*!
* @license
* Copyright 2017 Alfresco Software, Ltd.
*
* 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 {
ADMIN_USERNAME,
ADMIN_PASSWORD,
REPO_API_HOST,
REPO_API_TENANT
} from '../../configs';
export class RepoClientAuth {
static DEFAULT_USERNAME: string = ADMIN_USERNAME;
static DEFAULT_PASSWORD: string = ADMIN_PASSWORD;
constructor(
public username: string = RepoClientAuth.DEFAULT_USERNAME,
public password: string = RepoClientAuth.DEFAULT_PASSWORD
) {}
}
export class RepoClientConfig {
host?: string = REPO_API_HOST;
tenant?: string = REPO_API_TENANT;
}

View File

@ -0,0 +1,45 @@
/*!
* @license
* Copyright 2017 Alfresco Software, Ltd.
*
* 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 { RepoClientAuth, RepoClientConfig } from './repo-client-models';
import { PeopleApi } from './apis/people/people-api';
import { NodesApi } from './apis/nodes/nodes-api';
import { SitesApi } from './apis/sites/sites-api';
export class RepoClient {
public people: PeopleApi = new PeopleApi(this.auth, this.config);
public nodes: NodesApi = new NodesApi(this.auth, this.config);
public sites: SitesApi = new SitesApi(this.auth, this.config);
// public favorites: FavoritesApi = new FavoritesApi(this.auth, this.config);
// public shared: SharedLinksApi = new SharedLinksApi(this.auth, this.config);
constructor(
private username: string = RepoClientAuth.DEFAULT_USERNAME,
private password: string = RepoClientAuth.DEFAULT_PASSWORD,
private config?: RepoClientConfig
) {}
private get auth(): RepoClientAuth {
const { username, password } = this;
return { username, password };
}
}
export * from './apis/nodes/node-body-create';
export * from './apis/nodes/node-content-tree';
export * from './apis/nodes/nodes-api';

View File

@ -0,0 +1,71 @@
/*!
* @license
* Copyright 2017 Alfresco Software, Ltd.
*
* 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.
*/
/* tslint:disable */
const chalk = require('chalk');
/* tslint:enable */
export const log = {
i: 0,
get indentation(): string {
return new Array(this.i).fill(' ').join('');
},
indent() {
this.i++;
return this;
},
dedent() {
this.i--;
return this;
},
log(message: string = '', options: any = { ignoreIndentation: false }) {
const indentation = (!options.ignoreIndentation)
? this.indentation
: '';
console.log(`${indentation}${message}`);
return this;
},
blank() {
return this.log();
},
info(message: string = '', options: any = { bold: false, title: false }) {
const { bold } = options;
const style = (bold ? chalk.bold : chalk).gray;
return this.log(style(message), options);
},
success(message: string = '', options: any = { bold: false }) {
const style = options.bold ? chalk.bold.green : chalk.green;
return this.log(style(message), options);
},
error(message: string = '', options: any = { bold: false }) {
const style = options.bold ? chalk.bold.red : chalk.red;
return this.log(style(message), options);
}
};

View File

@ -0,0 +1,82 @@
/*!
* @license
* Copyright 2017 Alfresco Software, Ltd.
*
* 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 { log } from './console-logger';
const errors = [];
export const consoleReporter = {
jasmineStarted(suiteInfo) {
log.blank().info(
`Running ${suiteInfo.totalSpecsDefined} tests`,
{ bold: true, title: true }
).blank();
},
suiteStarted(suite) {
log.info(suite.description).indent();
},
specDone: (spec) => {
const {
status,
description,
failedExpectations
} = spec;
if (status === 'passed') {
log.success(`${description}`);
}
if (status === 'failed') {
log.error(`${description}`, { bold: true });
errors.push(spec);
failedExpectations.forEach((failed) => {
log.error(` ${failed.message}`);
});
}
},
suiteDone: (result) => {
log.dedent();
},
jasmineDone: (result) => {
if (!!errors.length) {
log .blank()
.blank()
.info(`${errors.length} failing tests`, { bold: true, title: true });
errors.forEach(error => {
log .blank()
.error(`${error.fullName}`, { bold: true });
error.failedExpectations.forEach(failed => {
log .info(`${failed.message}`)
.blank()
.error(`${failed.stack}`);
});
});
} else {
log.success(`All tests passed!`, { bold: true });
}
log.blank().blank();
}
};

View File

@ -0,0 +1,55 @@
/*!
* @license
* Copyright 2017 Alfresco Software, Ltd.
*
* 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.
*/
interface RequestConfig {
timeout?: number;
noDelay?: boolean;
keepAlive?: boolean;
keepAliveDelay?: number;
}
interface ResponseConfig {
timeout?: number;
}
interface ResponseRequest {
method: string;
path: string;
data: string;
}
export interface NodeRestClient {
get(uri: string, callback: Function): Function;
post(uri: string, callback: Function): Function;
put(uri: string, callback: Function): Function;
delete(uri: string, callback: Function): Function;
}
export interface RestClientArgs {
data?: any;
parameters?: any;
headers?: any;
requestConfig?: RequestConfig;
responseConfig?: ResponseConfig;
}
export interface RestClientResponse {
request: ResponseRequest;
data: any;
statusMessage: string;
statusCode: number;
}

View File

@ -0,0 +1,81 @@
/*!
* @license
* Copyright 2017 Alfresco Software, Ltd.
*
* 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 { Client } from 'node-rest-client';
import { NodeRestClient, RestClientArgs, RestClientResponse } from './rest-client-models';
export * from './rest-client-models';
export class RestClient {
private static DEFAULT_HEADERS = {
'Content-Type': 'application/json',
'Accept': 'application/json'
};
private client: NodeRestClient;
constructor(user: string, password: string) {
this.client = <NodeRestClient>(new Client({ user, password }));
}
get(uri: string, args: RestClientArgs = {}): Promise<RestClientResponse> {
return this.promisify('get', uri, args);
}
post(uri: string, args: RestClientArgs = {}): Promise<RestClientResponse> {
return this.promisify('post', uri, args);
}
put(uri: string, args: RestClientArgs = {}): Promise<RestClientResponse> {
return this.promisify('put', uri, args);
}
delete(uri: string, args: RestClientArgs = {}): Promise<RestClientResponse> {
return this.promisify('delete', uri, args);
}
private createArgs(args: RestClientArgs = {}): RestClientArgs {
const data = JSON.stringify(args.data);
return Object.assign({}, RestClient.DEFAULT_HEADERS, args, { data });
}
private promisify(fnName: string, uri: string, args: RestClientArgs): Promise<RestClientResponse> {
const fn: Function = this.client[fnName];
const fnArgs = [ encodeURI(uri), this.createArgs(args) ];
return new Promise((resolve, reject) => {
const fnCallback = (data, rawResponse) => {
const {
statusCode, statusMessage,
req: { method, path }
} = rawResponse;
const response: RestClientResponse = {
data, statusCode, statusMessage,
request: { method, path, data: args.data }
};
(response.statusCode >= 400)
? reject(response)
: resolve(response);
};
fn(...fnArgs, fnCallback);
});
}
}

View File

@ -6,7 +6,7 @@ const { SpecReporter } = require('jasmine-spec-reporter');
exports.config = { exports.config = {
allScriptsTimeout: 11000, allScriptsTimeout: 11000,
specs: [ specs: [
'./e2e/**/*.e2e-spec.ts' './e2e/**/*.test.ts'
], ],
capabilities: { capabilities: {
'browserName': 'chrome' 'browserName': 'chrome'