diff --git a/tests/e2e/pom.xml b/tests/e2e/pom.xml index 15f77b606..7edcffa0b 100644 --- a/tests/e2e/pom.xml +++ b/tests/e2e/pom.xml @@ -57,6 +57,58 @@ + + org.apache.maven.plugins + maven-failsafe-plugin + 2.20 + + + **/**IT.java + + + ${wildfly.port} + ./node_modules/protractor/bin/protractor + protractor.conf.js --params.upload.filePath ${project.build.directory}/deployments/rhamt-web.war --params.visualRegression true + ${project.build.directory}/npm + protractor.conf.js + ${project.build.directory}/deployments/rhamt-web.war + true + ${project.build.directory}/e2e-output.log + + false + false + + + + failsafe-integration-tests + integration-test + + integration-test + + + + failsafe-verify + verify + + verify + + + + + + org.apache.maven.plugins + maven-compiler-plugin + 3.6.1 + + + test-compile + + testCompile + + + + + maven-resources-plugin @@ -82,6 +134,46 @@ + + copy-database + test-compile + + copy-resources + + + ${project.build.directory}/${wildfly.directory}/standalone/data/windup/h2 + + + src/main/resources/h2 + false + + windup-web.h2.db + + + + + + + + + org.apache.maven.plugins + maven-antrun-plugin + 1.8 + + + unzip-reports + test-compile + + run + + + + + + + + + @@ -146,7 +238,7 @@ start-wildfly - test-compile + pre-integration-test start execute-commands @@ -170,7 +262,7 @@ stop-wildfly - package + post-integration-test shutdown @@ -232,32 +324,69 @@ ${project.build.directory/webdriver-update.log} - + + + run-e2e-visual-regression + integration-test + + exec + + + ${skipTests} + + + + 1 + + ./node_modules/protractor/bin/protractor + ${project.build.directory}/npm protractor.conf.js --params.upload.filePath ${project.build.directory}/deployments/rhamt-web.war - ${project.build.directory}/e2e-output.log + + make-results-report + integration-test + + exec + + + ${skipTests} + + ./verify-results.js + ${project.build.directory}/npm + + ${project.build.directory}/npm/test-results.xml + ${project.build.directory}/failsafe-reports/failsafe-summary.xml + + ${project.build.directory}/e2e-verify-results.log + + - diff --git a/tests/e2e/src/main/npm/e2e/pages/confirm-dialog.po.ts b/tests/e2e/src/main/npm/e2e/pages/confirm-dialog.po.ts index 683fb0dea..1921a6da0 100644 --- a/tests/e2e/src/main/npm/e2e/pages/confirm-dialog.po.ts +++ b/tests/e2e/src/main/npm/e2e/pages/confirm-dialog.po.ts @@ -1,12 +1,18 @@ -import {browser, by, element, ElementFinder} from "protractor"; +import {$, by, element} from "protractor"; export class ConfirmDialogPage { - confirmButton = element(by.css('.confirm-button')); - modal = element(by.css('.modal')); + confirmButton = $('.confirm-button'); + modal = $('.modal'); textInput = element(by.id('resource-to-delete')); isPresent() { - return this.modal.isPresent(); + return Promise.all([ + this.modal.isPresent(), + this.modal.getAttribute('class') + ]).then((result) => { + console.log('IsPresent: ', result); + return result[0] && result[1].search('in') !== -1; + }, error => console.error('Error in isPresent', error)); } requiresText(): Promise { diff --git a/tests/e2e/src/main/npm/e2e/pages/login.po.ts b/tests/e2e/src/main/npm/e2e/pages/login.po.ts index 1d639382d..28b5b66c2 100644 --- a/tests/e2e/src/main/npm/e2e/pages/login.po.ts +++ b/tests/e2e/src/main/npm/e2e/pages/login.po.ts @@ -1,16 +1,16 @@ -import {browser, by, element} from "protractor"; +import {$, browser} from "protractor"; /** * TODO: This seems to be required in onPrepare phase, I'm not sure if it can be used from this file * (it would be probably better) */ export class LoginPage { - username = element(by.css('#username')); - password = element(by.css('#password')); - loginButton = element(by.css('#kc-login')); + username = $('#username'); + password = $('#password'); + loginButton = $('#kc-login'); public navigateTo() { - browser.get('/rhamt-web/'); + return browser.get('/rhamt-web/'); } public login(user: string, password: string) { diff --git a/tests/e2e/src/main/npm/e2e/pages/project-level/analysis-config.po.ts b/tests/e2e/src/main/npm/e2e/pages/project-level/analysis-config.po.ts index c4276740c..0ef9820fc 100644 --- a/tests/e2e/src/main/npm/e2e/pages/project-level/analysis-config.po.ts +++ b/tests/e2e/src/main/npm/e2e/pages/project-level/analysis-config.po.ts @@ -1,6 +1,31 @@ -import {browser, by, element, ElementFinder} from "protractor"; +import {$, $$, browser} from "protractor"; export class AnalysisConfigurationPage { + protected transformationPaths = $$('.radio-inline.control-label input'); -} + targets = { + EAP7: this.transformationPaths.first(), + EAP6: this.transformationPaths.get(1), + CloudReadiness: this.transformationPaths.get(2) + }; + + enableCloudReadinessInput = $('[name="cloudTargetsIncluded"]'); + + selectedApplications = $$('.search-choice'); + + packages = $$('wu-js-tree-wrapper'); + + includedPackages = this.packages.first().$$('.jstree-node[aria-selected=true]'); + excludedPackages = this.packages.get(1).$$('.jstree-node[aria-selected=true]'); + + saveAndRunButton = $('.btn-save-run'); + + public navigateTo(projectId: number) { + return browser.get(`projects/${projectId}/analysis-context`); + } + + public saveAndRun() { + return this.saveAndRunButton.click(); + } +} diff --git a/tests/e2e/src/main/npm/e2e/pages/project-level/analysis-list.po.ts b/tests/e2e/src/main/npm/e2e/pages/project-level/analysis-list.po.ts index 84ac97eda..9990dd62c 100644 --- a/tests/e2e/src/main/npm/e2e/pages/project-level/analysis-list.po.ts +++ b/tests/e2e/src/main/npm/e2e/pages/project-level/analysis-list.po.ts @@ -1,43 +1,54 @@ -import {browser, by, element, ElementFinder} from "protractor"; +import {$, $$, browser, by, element, ElementFinder} from "protractor"; export class AnalysisListPage { searchTextBox = element(by.name('searchValue')); - searchClearButton = element(by.css('wu-search button.clear')); + searchClearButton = $('wu-search button.clear'); - table = element(by.css('table.executions-list-table')); - tableHeaders = this.table.all(by.css('th')); + table = $('table.executions-list-table'); + tableHeaders = this.table.$$('th'); - emptyDiv = element(by.css('.blank-slate-pf-main-action')); + emptyDiv = $('.blank-slate-pf-main-action'); - configureAnalysisButton = element(by.css('.btn.btn-primary')); + configureAnalysisButton = $('.btn.btn-primary'); - public getExecutions(): Promise { + progressbar = $('.progress-container'); + + noActiveAnalysisText = $$('.progressbar-no-active-analysis'); + + public getExecutions(): Promise { return this.table.isPresent().then(isPresent => { if (!isPresent) { return []; } - return this.table.all(by.css('tr')).then((tableRows: ElementFinder[]): any => Promise.all(tableRows.map(row => { - const tableColumns = row.all(by.css('td')); - - const execution = { - id: '', - status: '', - applications: '', - dateStarted: '', - actions: { - showAnalysisDetail: tableColumns.get(0), - showReport: tableColumns.get(4).all(by.css('a')).get(0), - delete: tableColumns.get(4).all(by.css('a')).get(1) + return this.table.$('tbody').$$('tr').then((tableRows: ElementFinder[]): any => Promise.all(tableRows.map(row => { + const tableColumns = row.$$('td'); + + return tableColumns.count().then(count => { + if (count == 0) { + return []; } - }; - - return Promise.all([ - tableColumns.get(0).getText().then(id => execution.id = id), - tableColumns.get(1).getText().then(status => execution.status = status), - tableColumns.get(2).getText().then(countApplications => execution.applications = countApplications), - tableColumns.get(3).getText().then(dateStarted => execution.dateStarted = dateStarted) - ]).then(() => execution); + + const execution = { + id: '', + status: '', + applications: '', + dateStarted: '', + actions: { + showAnalysisDetail: tableColumns.get(0), + showReport: tableColumns.get(4).$$('a').get(0), + delete: tableColumns.get(4).$$('a').get(1) + }, + row: row + }; + + return Promise.all([ + tableColumns.get(0).getText().then(id => execution.id = id), + tableColumns.get(1).getText().then(status => execution.status = status), + tableColumns.get(2).getText().then(countApplications => execution.applications = countApplications), + tableColumns.get(3).getText().then(dateStarted => execution.dateStarted = dateStarted) + ]).then(() => execution) as any; + }); }))); }); } @@ -75,4 +86,5 @@ export interface Execution { applications: string; dateStarted: string; actions: ExecutionActions; + row: ElementFinder; } diff --git a/tests/e2e/src/main/npm/e2e/pages/project-level/applications-list.po.ts b/tests/e2e/src/main/npm/e2e/pages/project-level/applications-list.po.ts index 1b34644bf..99a1e3f08 100644 --- a/tests/e2e/src/main/npm/e2e/pages/project-level/applications-list.po.ts +++ b/tests/e2e/src/main/npm/e2e/pages/project-level/applications-list.po.ts @@ -1,11 +1,11 @@ -import {browser, by, element, ElementFinder} from "protractor"; +import {$, browser, by, element, ElementFinder} from "protractor"; export class ApplicationsListPage { searchTextBox = element(by.name('searchValue')); - searchClearButton = element(by.css('wu-search button.clear')); + searchClearButton = $('wu-search button.clear'); - table = element(by.css('table')); - tableHeaders = this.table.all(by.css('th')); + table = $('table'); + tableHeaders = this.table.$$('th'); public getApplications(): Promise { return this.table.isPresent().then(isPresent => { @@ -13,10 +13,10 @@ export class ApplicationsListPage { return []; } - const tableRowsFinder = this.table.element(by.css('tbody')).all(by.css('tr')); + const tableRowsFinder = this.table.$('tbody').$$('tr'); return tableRowsFinder.then((tableRows: ElementFinder[]): any => Promise.all(tableRows.map(row => { - const tableColumns = row.all(by.css('td')); + const tableColumns = row.$$('td'); const application = { name: '', diff --git a/tests/e2e/src/main/npm/e2e/pages/project-level/context-menu.po.ts b/tests/e2e/src/main/npm/e2e/pages/project-level/context-menu.po.ts index 53fd69c7f..c546ac5f3 100644 --- a/tests/e2e/src/main/npm/e2e/pages/project-level/context-menu.po.ts +++ b/tests/e2e/src/main/npm/e2e/pages/project-level/context-menu.po.ts @@ -1,25 +1,72 @@ -import {by, element, ElementFinder} from "protractor"; +import {$$, ElementFinder} from "protractor"; export class ContextMenuPage { - menuItems = element.all(by.css('wu-context-menu .list-group-item')); + menuItems = $$('wu-context-menu .list-group-item'); public getMenuItems(): Promise { return this.menuItems.then((menuItems: ElementFinder[]): any => { return Promise.all(menuItems.map(item => { const menuLink = { label: '', - link: item.element(by.css('a')) + link: item.$('a') }; - return item.element(by.css('span.list-group-item-value')).getText() + return item.$('span.list-group-item-value').getText() .then(text => menuLink.label = text) .then(() => menuLink); })); }); } + + public openAnalysisConfig(): Promise { + return this.getMenuItems().then(items => { + const analysisConfig = items.find(item => item.label.search(new RegExp('Analysis Configuration', 'i')) !== -1); + + if (analysisConfig !== null) { + return analysisConfig.link.click(); + } else { + return new Promise((resolve, reject) => reject('Analysis Configuration not found')); + } + }); + } + + public openMenuItem(item: MenuItems|ReportLevelMenuItems): Promise { + return this.getMenuItems().then(items => { + if (items.length >= item) { + return items[item].link.click(); + } else { + return new Promise((resolve, reject) => reject(`Menu item ${item} not found`)); + } + }); + } + + public openSubmenuItem(menu: MenuItems|ReportLevelMenuItems, subMenu: any): Promise { + return this.openMenuItem(menu); + } } export interface ContextMenuItem { label: string; link: ElementFinder; } + +export enum MenuItems { + ANALYSIS_RESULTS = 0, + APPLICATIONS = 1, + ANALYSIS_CONFIG = 2 +} + +export enum ReportLevelMenuItems { + APPLICATION_LIST = 0, + DASHBOARD = 1, + TECHNOLOGIES_OVERALL = 2, + MIGRATION_ISSUES = 3, + EXECUTION_DETAILS = 4, + STATIC_REPORT = 5 +} + +export enum TechReportMenuItems { + TECHNOLOGIES_EJB = 0, + TECHNOLOGIES_REMOTE_SERVICES = 1, + TECHNOLOGIES_HIBERNATE = 2, +} diff --git a/tests/e2e/src/main/npm/e2e/pages/project-list.po.ts b/tests/e2e/src/main/npm/e2e/pages/project-list.po.ts new file mode 100644 index 000000000..cced5a300 --- /dev/null +++ b/tests/e2e/src/main/npm/e2e/pages/project-list.po.ts @@ -0,0 +1,71 @@ +import {$, $$, browser, ElementFinder} from "protractor"; + +export class ProjectListPage { + newProjectButton = $('.btn.btn-primary'); + projectList = $$('.list-group-item.project-info.tile-click'); + + projectListDiv = $('.projects-list'); + + emptyStateDiv = $('.blank-slate-pf'); + emptyStateRemoveFilter = $('.no-matches a'); + + searchInput = $('.search-pf-input-group input'); + searchRemoveButton = $('.pficon-close'); + + public navigateTo() { + return browser.get('./'); + } + + public search(text: string) { + return this.searchInput.sendKeys(text); + } + + public sort() {} + + public sortOrder() { + + } + + public newProject() { + return this.newProjectButton.click(); + } + + public editProject() { + } + + public deleteProject() { + } + + public getProjectList(): Promise { + return (this.projectList.then((elements: ElementFinder[]): any => { + return Promise.all(elements.map((element, index, array) => { + const project = { + name: '', + description: '', + countApplications: '', + lastUpdated: '', + editButton: element.$('.action-edit-project'), + deleteButton: element.$('.action-delete-project'), + projectDiv: element.$('.project-info') + }; + + return Promise.all([ + element.$('.project-title').getText().then(title => project.name = title), + element.$('.count-applications').getText().then(count => project.countApplications = count), + element.$('.last-updated').getText().then(lastUpdated => project.lastUpdated = lastUpdated), + element.$('.description').getText().then(description => project.description = description), + ]).then(() => project); + })); + })); + } +} + +export interface Project { + name: string; + description: string; + countApplications: string; + lastUpdated: string; + editButton; + deleteButton; + projectDiv?: any; +} diff --git a/tests/e2e/src/main/npm/e2e/pages/project.po.ts b/tests/e2e/src/main/npm/e2e/pages/project.po.ts deleted file mode 100644 index 551420094..000000000 --- a/tests/e2e/src/main/npm/e2e/pages/project.po.ts +++ /dev/null @@ -1,64 +0,0 @@ -import {browser, by, element, ElementFinder} from "protractor"; - -export class ProjectPage { - newProjectButton = element(by.css('.btn.btn-primary')); - projectList = element.all(by.css('.list-group-item.project-info.tile-click')); - - projectListDiv = element(by.css('.projects-list')); - - emptyStateDiv = element(by.css('.blank-slate-pf')); - - public navigateTo() { - return browser.get('./'); - } - - public search(text: string) { - } - - public sort() {} - - public sortOrder() { - - } - - public newProject() { - return this.newProjectButton.click(); - } - - public editProject() { - } - - public deleteProject() { - } - - public getProjectList(): Promise { - return (this.projectList.then((elements: ElementFinder[]): any => { - return Promise.all(elements.map((element, index, array) => { - const project = { - name: '', - description: '', - countApplications: '', - lastUpdated: '', - editButton: element.element(by.css('.action-edit-project')), - deleteButton: element.element(by.css('.action-delete-project')) - }; - - return Promise.all([ - element.element(by.css('.project-title')).getText().then(title => project.name = title), - element.element(by.css('.count-applications')).getText().then(count => project.countApplications = count), - element.element(by.css('.last-updated')).getText().then(lastUpdated => project.lastUpdated = lastUpdated), - element.element(by.css('.description')).getText().then(description => project.description = description), - ]).then(() => project); - })); - })); - } -} - -export interface Project { - name: string; - description: string; - countApplications: string; - lastUpdated: string; - editButton; - deleteButton; -} diff --git a/tests/e2e/src/main/npm/e2e/pages/wizard/add-applications.po.ts b/tests/e2e/src/main/npm/e2e/pages/wizard/add-applications.po.ts index 65f6c29e1..f832a0c1e 100644 --- a/tests/e2e/src/main/npm/e2e/pages/wizard/add-applications.po.ts +++ b/tests/e2e/src/main/npm/e2e/pages/wizard/add-applications.po.ts @@ -1,14 +1,14 @@ -import {browser, by, element} from "protractor"; +import {$, browser, by, element} from "protractor"; export class AddApplicationsPage { title = element(by.id('idProjectTitle')); description = element(by.id('idDescription')); - serverPathButton = element(by.css('wu-tab-container ul li:nth-child(2)')); + serverPathButton = $('wu-tab-container ul li:nth-child(2)'); serverPathInput = element(by.id('appPathToRegister')); - cancel = element(by.css('.btn.btn-default')); - next = element(by.css('.btn.btn-primary')); + cancel = $('.btn.btn-default'); + next = $('.btn.btn-primary'); public uploadFile() { /* // set file detector @@ -19,7 +19,7 @@ export class AddApplicationsPage { var fileToUpload = '../sample.txt'; var absolutePath = path.resolve(__dirname, fileToUpload); - var fileElem = element(by.css('input[type="file"]')); + var fileElem = $('input[type="file"]'); // Unhide file input browser.executeScript("arguments[0].style.visibility = 'visible'; arguments[0].style.height = '1px'; arguments[0].style.width = '1px'; arguments[0].style.opacity = 1", fileElem.getWebElement()); @@ -30,7 +30,7 @@ export class AddApplicationsPage { browser.driver.sleep(100); // click upload button - element(by.css('button[data-ng-click="uploadFile(file)"]')).click(); // does post request + $('button[data-ng-click="uploadFile(file)"]').click(); // does post request */ } diff --git a/tests/e2e/src/main/npm/e2e/pages/wizard/analysis-configuration.po.ts b/tests/e2e/src/main/npm/e2e/pages/wizard/analysis-configuration.po.ts index 1d2a052e6..df8dcc049 100644 --- a/tests/e2e/src/main/npm/e2e/pages/wizard/analysis-configuration.po.ts +++ b/tests/e2e/src/main/npm/e2e/pages/wizard/analysis-configuration.po.ts @@ -1,8 +1,8 @@ -import {browser, by, element} from "protractor"; +import {$, browser, by, element} from "protractor"; export class AnalysisConfigurationPage { - saveAndRun = null; //element(by.css('.btn.btn-primary:nth-child(3)')); - save = element(by.css('.btn-save')); // by.buttonText('Save')); // .css('.btn-primary')); // :nth-child(2) + saveAndRun = null; //$('.btn.btn-primary:nth-child(3)'); + save = $('.btn-save'); // by.buttonText('Save')); // .css('.btn-primary'); // :nth-child(2) public clickSave() { return this.save.click(); diff --git a/tests/e2e/src/main/npm/e2e/pages/wizard/create-project.po.ts b/tests/e2e/src/main/npm/e2e/pages/wizard/create-project.po.ts index 3695a15bc..fe1110cce 100644 --- a/tests/e2e/src/main/npm/e2e/pages/wizard/create-project.po.ts +++ b/tests/e2e/src/main/npm/e2e/pages/wizard/create-project.po.ts @@ -1,11 +1,11 @@ -import {by, element} from "protractor"; +import {$, browser, by, element} from "protractor"; export class CreateProjectPage { title = element(by.id('idProjectTitle')); description = element(by.id('idDescription')); - cancel = element(by.css('.btn.btn-default')); - next = element(by.css('.btn.btn-primary')); + cancel = $('.btn.btn-default'); + next = $('.btn.btn-primary'); public setTitle(title: string) { return this.title.sendKeys(title); @@ -18,4 +18,8 @@ export class CreateProjectPage { public clickNext() { return this.next.click(); } + + public navigateTo() { + return browser.get('./wizard/create-project'); + } } diff --git a/tests/e2e/src/main/npm/e2e/project-list-empty.e2e.ts b/tests/e2e/src/main/npm/e2e/project-list-empty.e2e.ts deleted file mode 100644 index 3becf2de6..000000000 --- a/tests/e2e/src/main/npm/e2e/project-list-empty.e2e.ts +++ /dev/null @@ -1,27 +0,0 @@ -/* -import {ProjectPage} from "./pages/project.po"; - - -describe('Project List', () => { - const projectPage = new ProjectPage(); - - xdescribe('Without any projects', () => { - beforeAll(() => { - projectPage.navigateTo(); - // TODO: Ensure no project exists - }); - - it('Should not show project list', () => { - expect(projectPage.projectListDiv.isPresent()).toBeFalsy(); - }); - - it('Should show New project button', () => { - expect(projectPage.newProjectButton.isPresent()).toBeTruthy(); - }); - - it('Should show welcome message', () => { - expect(projectPage.emptyStateDiv.isPresent()).toBeTruthy(); - }); - }); -}); -*/ diff --git a/tests/e2e/src/main/npm/e2e/project-without-analysis.e2e.ts b/tests/e2e/src/main/npm/e2e/project-without-analysis.e2e.ts deleted file mode 100644 index cd49528a1..000000000 --- a/tests/e2e/src/main/npm/e2e/project-without-analysis.e2e.ts +++ /dev/null @@ -1,96 +0,0 @@ -import {CreateProjectWorkflow} from "./workflows/create-project.wf"; -import {browser, by, element} from "protractor"; -import {ApplicationsListPage} from "./pages/project-level/applications-list.po"; -import {ContextMenuPage} from "./pages/project-level/context-menu.po"; -import {AnalysisListPage} from "./pages/project-level/analysis-list.po"; - -describe('For project without any analysis', () => { - const project = { - id: 0, - name: '', - }; - - const contextMenu = new ContextMenuPage(); - - beforeAll(() => { - /** - * Create project - */ - const workflow = new CreateProjectWorkflow(); - workflow.createProjectWithTimeInName() - .then(() => browser.waitForAngular()) - .then(() => { - browser.getCurrentUrl().then(() => { - - }); - }); - }); - - it('should get redirected to analysis list', () => { - expect(browser.getCurrentUrl()).toContain('project-detail'); - }); - - it('should show context menu', () => { - contextMenu.getMenuItems().then(menuItems => { - expect(menuItems[0].label).toEqual('Analysis Results'); - expect(menuItems[1].label).toEqual('Applications'); - expect(menuItems[2].label).toEqual('Analysis Configuration'); - }); - }); - - describe('application list page', () => { - const applicationsList = new ApplicationsListPage(); - - beforeAll(() => { - contextMenu.getMenuItems().then(items => { - items[1].link.click(); - }); - }); - - it('should contain uploaded application', () => { - applicationsList.getApplications().then(applications => { - expect(applications.length).toBe(1); -// expect(applications).toContain(''); - }); - }); - }); - - describe('analysis list page', () => { - const analysisPage = new AnalysisListPage(); - - beforeAll(() => { - contextMenu.getMenuItems().then(items => { - items[0].link.click(); - }) - .then(() => browser.waitForAngular()); - }); - - it('should show empty message', () => { - expect(analysisPage.emptyDiv.isPresent()).toBeTruthy(); - }); - }); - - describe('analysis configuration page', () => { - beforeAll(() => { - contextMenu.getMenuItems().then(items => { - items[2].link.click(); - }) - .then(() => browser.waitForAngular()) - .then(() => {}); - }); - - it('should have EAP 7 selected', () => {}); - - it('should not have cloud readiness selected', () => {}); - - it('should have all applications selected', () => {}); - - it('should not have any packages included', () => {}); - - it('should not show exclude packages', () => {}); - - it('should not show custom rules', () => {}); - - it('should not show advanced options', () => {}); - }); -}); diff --git a/tests/e2e/src/main/npm/e2e/tests/analysis.e2e.ts b/tests/e2e/src/main/npm/e2e/tests/analysis.e2e.ts new file mode 100644 index 000000000..c2c21e3b0 --- /dev/null +++ b/tests/e2e/src/main/npm/e2e/tests/analysis.e2e.ts @@ -0,0 +1,140 @@ +import {browser} from "protractor"; +import {CreateProjectWorkflow} from "../workflows/create-project.wf"; +import {ContextMenuPage} from "../pages/project-level/context-menu.po"; +import {AnalysisListPage, Execution} from "../pages/project-level/analysis-list.po"; +import {AnalysisWorkflow} from "../workflows/analysis.wf"; +import {waitUntilCondition} from "../utils/async"; + +describe('For project with analysis', () => { + const project = { + id: 0, + name: '', + }; + + const contextMenu = new ContextMenuPage(); + const analysisList = new AnalysisListPage(); + const analysisWorkflow = new AnalysisWorkflow(); + + let executions: Execution[]; + + beforeAll((done) => { + console.error('Creating project with analysis.....'); + browser.waitForAngular(); + /** + * Create project + */ + const workflow = new CreateProjectWorkflow(); + workflow.createProjectWithTimeInName() + .then(() => browser.waitForAngular()) + .then(() => console.error('Starting analysis....')) + .then(() => analysisWorkflow.startAnalysisFromProjectPage()) + .then(() => done()); + // .then(result => console.log(result), error => console.error(error)); + }); + + describe('running analysis', () => { + describe('analysis list page', () => { + beforeAll((done) => { + analysisList.getExecutions() + .then(executionList => executions = executionList) + .then(() => done()); + }); + + it('should contain active analysis in table', () => { + console.error(executions); + + const isQueued = executions[0].status.search('Queued') !== -1; + const isInProgress = executions[0].status.search('progress') !== -1; + + expect(executions.length).toBe(1); + expect(isQueued || isInProgress).toBeTruthy(); + }); + + it('should have blue table row for active analysis', (done) => { + executions[0].row.getAttribute('class').then(cssClass => { + expect(cssClass.search('info') !== -1).toBeTruthy(); + done(); + }); + }); + + it('should have analysis progress bar', (done) => { + console.error('Wait until analysis is queued.......'); + waitUntilCondition(200,() => { + return analysisList.getExecutions().then(executionList => { + return executionList.length == 1 && executionList[0].status.search('progress') !== -1; + }); + }, 3000) + .then(() => { + browser.waitForAngularEnabled(true); + return analysisList.getExecutions(); + }).then(executionList => { + executions = executionList; + }).then(() => analysisList.progressbar.isPresent()) + .then(isProgressbarPresent => { + expect(isProgressbarPresent).toBeTruthy(); + done(); + }); + }); + }); + }); + + describe('finished analysis', () => { + beforeAll((done) => { + /** + * TODO: Wait until analysis finishes + */ + let isAnalysisRunning = true; + + browser.waitForAngularEnabled(false); + + const waitForExecutionToFinish = (timeout: number, maxTimeout?: number, currentTimeout: number = 0): Promise => { + console.error('WaitForExecutionToFinish looping: ' + `timeout: ${timeout}, maxTimeout: ${maxTimeout}, current: ${currentTimeout}`); + + if (maxTimeout != 0 && currentTimeout >= maxTimeout) { + console.error('Max wait time exceeded, exiting'); + + return new Promise((resolve, reject) => { + reject('Max wait time exceeded'); + }); + } + + return browser.driver.sleep(timeout).then(() => analysisList.getExecutions() as any).then(executionsList => { + if (executionsList.length === 1 && executionsList[0].status.search('Completed') !== -1) { + isAnalysisRunning = false; + } else { + return waitForExecutionToFinish(timeout, maxTimeout, currentTimeout + timeout); + } + }); + }; + + console.error('Wait until analysis finishes.......'); + waitForExecutionToFinish(5 * 1000, 5 * 1000 * 60).then(() => { + console.error('Analysis is finished, continuing.......'); + browser.waitForAngularEnabled(true); + return analysisList.getExecutions(); + }).then(executionList => { + executions = executionList; + done(); + }); + }); + + it('should have completed status', () => { + expect(executions.length).toBe(1); + expect(executions[0].status.search('Completed') !== -1).toBeTruthy(); + }); + + it('should have report link', (done) => { + executions[0].actions.showReport.isPresent().then(hasReportLink => { + expect(hasReportLink).toBeTruthy(); + done(); + }); + }); + + it('should be green in table', (done) => { + executions[0].row.getAttribute('class').then(cssClass => { + expect(cssClass.search('success') !== -1).toBeTruthy(); + done(); + }); + }); + }); +}); diff --git a/tests/e2e/src/main/npm/e2e/tests/project-list/project-list-empty.e2e.ts b/tests/e2e/src/main/npm/e2e/tests/project-list/project-list-empty.e2e.ts new file mode 100644 index 000000000..a63c66137 --- /dev/null +++ b/tests/e2e/src/main/npm/e2e/tests/project-list/project-list-empty.e2e.ts @@ -0,0 +1,34 @@ +import {ProjectListPage} from "../../pages/project-list.po"; +import {CleanupWorkflow} from "../../workflows/cleanup.wf"; + +/** + * TODO: How to test this? We need to have precondition of empty project list to test this. + */ +xdescribe('Empty project list', () => { + const projectPage = new ProjectListPage(); + + /* + * Given there are no projects + */ + beforeAll((done) => { + projectPage.navigateTo(); + + const cleanup = new CleanupWorkflow(); + + projectPage.getProjectList() + .then(projects => projects.map(project => cleanup.deleteProject(project))) + .then(promises => Promise.all(promises).then(() => done())); + }); + + it('Should not show project list', () => { + expect(projectPage.projectListDiv.isPresent()).toBeFalsy(); + }); + + it('Should show New project button', () => { + expect(projectPage.newProjectButton.isPresent()).toBeTruthy(); + }); + + it('Should show welcome message', () => { + expect(projectPage.emptyStateDiv.isPresent()).toBeTruthy(); + }); +}); diff --git a/tests/e2e/src/main/npm/e2e/tests/project-list/project-list-filtering.e2e.ts b/tests/e2e/src/main/npm/e2e/tests/project-list/project-list-filtering.e2e.ts new file mode 100644 index 000000000..21be657e6 --- /dev/null +++ b/tests/e2e/src/main/npm/e2e/tests/project-list/project-list-filtering.e2e.ts @@ -0,0 +1,102 @@ +import {ProjectListPage} from "../../pages/project-list.po"; +import {CreateProjectWorkflow} from "../../workflows/create-project.wf"; +import {browser} from "protractor"; +import {CleanupWorkflow} from "../../workflows/cleanup.wf"; + +describe('Project List Filtering', () => { + const projectPage = new ProjectListPage(); + + let newlyCreatedProjects = []; + + beforeAll((done) => { + const date = new Date().getTime().toString(); + const wf = new CreateProjectWorkflow(); + + newlyCreatedProjects = [ + 'This has some matching text in title ' + date, + 'This does not ' + date + ]; + + projectPage.navigateTo() + .then(() => wf.createProject(newlyCreatedProjects[0])) + .then(() => wf.createProject(newlyCreatedProjects[1], 'But it has some matching text in description')) + .then(() => projectPage.navigateTo()) + .then(() => done()); + }); + + describe('with matching input', () => { + let searchText; + let searchRegex; + + beforeAll((done) => { + searchText = 'some matching text'; + searchRegex = new RegExp(searchText, 'i'); + projectPage.search(searchText) + .then(() => browser.waitForAngular()) + .then(() => done()); + }); + + it('should find relevant projects', (done) => { + projectPage.getProjectList().then(projects => { + expect(projects.length).toBe(2); + done(); + }); + }); + + it('should filter projects matching title', (done) => { + projectPage.getProjectList().then(projects => { + expect(projects.some(project => { + return project.name.search(searchRegex) != -1 + })); + done(); + }); + }); + + it('should filter projects matching description', (done) => { + projectPage.getProjectList().then(projects => { + expect(projects.some(project => project.description.search(searchRegex) != -1)); + done(); + }); + }); + }); + + describe('with not matching input', () => { + beforeAll((done) => { + projectPage.search('t*h*i*s*-i*s*-c*o*m*p*l*e*t*e*-n*o*n*s*e*n*s*e*') + .then(() => done()); + }); + + it('should show empty filter message', () => { + /*projectPage.emptyStateDiv.isPresent().then(isPresent => { + expect(isPresent).toBeTruthy(); + });*/ + + expect(projectPage.emptyStateDiv.isPresent()).toBeTruthy(); + }); + + describe('clicking on remove filter', () => { + beforeAll((done) => { + projectPage.emptyStateRemoveFilter.click() + .then(() => done()); + }); + + it('should remove filter', (done) => { + projectPage.searchInput.getText().then(text => { + expect(text).toBe(''); + done(); + }); + }); + }); + }); + + + afterAll( (done) => { + const cleanupWf = new CleanupWorkflow(); + + projectPage.getProjectList().then(projects => { + const chainedPromises = newlyCreatedProjects.map(projectName => cleanupWf.deleteProjectFromList(projectName, projects)); + + return Promise.all(chainedPromises); + }).then(() => done()); + }); +}); diff --git a/tests/e2e/src/main/npm/e2e/tests/project-list/project-list-sorting.e2e.ts b/tests/e2e/src/main/npm/e2e/tests/project-list/project-list-sorting.e2e.ts new file mode 100644 index 000000000..c96a71bf0 --- /dev/null +++ b/tests/e2e/src/main/npm/e2e/tests/project-list/project-list-sorting.e2e.ts @@ -0,0 +1,15 @@ +import {ProjectListPage} from "../../pages/project-list.po"; + +describe('Project List Sorting', () => { + const projectPage = new ProjectListPage(); + + describe('default sorting', () => { + it('should sort by name', () => { + + }); + + it('should sort in ASC order', () => { + + }); + }); +}); diff --git a/tests/e2e/src/main/npm/e2e/project-list.e2e.ts b/tests/e2e/src/main/npm/e2e/tests/project-list/project-list.e2e.ts similarity index 80% rename from tests/e2e/src/main/npm/e2e/project-list.e2e.ts rename to tests/e2e/src/main/npm/e2e/tests/project-list/project-list.e2e.ts index 696beeea5..58f1bd84a 100644 --- a/tests/e2e/src/main/npm/e2e/project-list.e2e.ts +++ b/tests/e2e/src/main/npm/e2e/tests/project-list/project-list.e2e.ts @@ -1,10 +1,9 @@ -import {ProjectPage} from "./pages/project.po"; -import {CreateProjectWorkflow} from "./workflows/create-project.wf"; -import {ConfirmDialogPage} from "./pages/confirm-dialog.po"; +import {ProjectListPage} from "../../pages/project-list.po"; +import {CreateProjectWorkflow} from "../../workflows/create-project.wf"; import {browser} from "protractor"; describe('Project List', () => { - const projectPage = new ProjectPage(); + const projectPage = new ProjectListPage(); describe('With projects', () => { let projectName: string; @@ -16,8 +15,6 @@ describe('Project List', () => { const date = new Date(); projectName = 'Test ' + date.getTime().toString(); - console.error('MOST UP TO DATE'); - workflow.createProject(projectName) .then(() => projectPage.navigateTo()) .then(() => browser.waitForAngular()) @@ -32,19 +29,23 @@ describe('Project List', () => { expect(projectPage.projectListDiv.isPresent()).toBeTruthy(); }); + it('Should contain at least 2 projects', () => { + + }); +/* it('Should contain just created project', () => { projectListPromise.then(projects => { expect(projects.some(item => item.name === projectName)).toBeTruthy(); }); }); - - afterAll(async () => { - await projectListPromise.then(projects => { +*/ + /* + afterAll((done) => { + projectListPromise.then(projects => { let project = projects.find(item => item.name === projectName); if (project != null) { project.deleteButton.click().then(() => { - console.log('delete'); const confirmDialog = new ConfirmDialogPage(); confirmDialog.requiresText().then(requiresText => { @@ -56,7 +57,8 @@ describe('Project List', () => { }); }); } - }); + }).then(() => done()); }); + */ }); }); diff --git a/tests/e2e/src/main/npm/e2e/tests/project/create-project.e2e.ts b/tests/e2e/src/main/npm/e2e/tests/project/create-project.e2e.ts new file mode 100644 index 000000000..e69de29bb diff --git a/tests/e2e/src/main/npm/e2e/tests/project/project-with-analysis.e2e.ts b/tests/e2e/src/main/npm/e2e/tests/project/project-with-analysis.e2e.ts new file mode 100644 index 000000000..d18b66867 --- /dev/null +++ b/tests/e2e/src/main/npm/e2e/tests/project/project-with-analysis.e2e.ts @@ -0,0 +1,43 @@ +import {ContextMenuPage} from "../../pages/project-level/context-menu.po"; +import {AnalysisListPage, Execution} from "../../pages/project-level/analysis-list.po"; +import {AnalysisWorkflow} from "../../workflows/analysis.wf"; +import {PROJECT_WITH_ANALYSIS} from "../../utils/data"; + +describe('For project with finished analysis', () => { + const contextMenu = new ContextMenuPage(); + const analysisList = new AnalysisListPage(); + const analysisWorkflow = new AnalysisWorkflow(); + + let executions: Execution[]; + + beforeAll((done) => { + analysisList.navigateTo(PROJECT_WITH_ANALYSIS.id) + .then(() => analysisList.getExecutions() as any) + .then(executionList => { + executions = executionList; + done(); + }); + }); + + it('should have 1 execution', () => { + expect(executions.length).toBe(1); + }); + + it('should have completed status', () => { + expect(executions[0].status.search('Completed') !== -1).toBeTruthy(); + }); + + it('should have report link', (done) => { + executions[0].actions.showReport.isPresent().then(hasReportLink => { + expect(hasReportLink).toBeTruthy(); + done(); + }); + }); + + it('should be green in table', (done) => { + executions[0].row.getAttribute('class').then(cssClass => { + expect(cssClass.search('success') !== -1).toBeTruthy(); + done(); + }); + }); +}); diff --git a/tests/e2e/src/main/npm/e2e/tests/project/project-without-analysis.e2e.ts b/tests/e2e/src/main/npm/e2e/tests/project/project-without-analysis.e2e.ts new file mode 100644 index 000000000..db646d480 --- /dev/null +++ b/tests/e2e/src/main/npm/e2e/tests/project/project-without-analysis.e2e.ts @@ -0,0 +1,111 @@ +import {browser} from "protractor"; +import {ApplicationsListPage} from "../../pages/project-level/applications-list.po"; +import {ContextMenuPage} from "../../pages/project-level/context-menu.po"; +import {AnalysisListPage} from "../../pages/project-level/analysis-list.po"; +import {AnalysisConfigurationPage} from "../../pages/project-level/analysis-config.po"; +import {PROJECT_WITHOUT_ANALYSIS} from "../../utils/data"; + +describe('For project without any analysis', () => { + const contextMenu = new ContextMenuPage(); + + beforeAll((done) => { + /** + * Navigate to project + */ + const analysisList = new AnalysisListPage(); + analysisList.navigateTo(PROJECT_WITHOUT_ANALYSIS.id) + .then(() => browser.waitForAngular()) + .then(() => done()); + }); + + it('should show context menu', () => { + contextMenu.getMenuItems().then(menuItems => { + expect(menuItems[0].label).toEqual('Analysis Results'); + expect(menuItems[1].label).toEqual('Applications'); + expect(menuItems[2].label).toEqual('Analysis Configuration'); + }); + }); + + describe('application list page', () => { + const applicationsList = new ApplicationsListPage(); + + beforeAll(() => { + contextMenu.getMenuItems().then(items => { + items[1].link.click(); + }); + }); + + it('should contain uploaded application', () => { + applicationsList.getApplications().then(applications => { + expect(applications.length).toBe(1); + }); + }); + }); + + describe('analysis list page', () => { + const analysisPage = new AnalysisListPage(); + + beforeAll(() => { + contextMenu.getMenuItems().then(items => { + items[0].link.click(); + }) + .then(() => browser.waitForAngular()); + }); + + it('should show empty message', () => { + expect(analysisPage.emptyDiv.isPresent()).toBeTruthy(); + }); + }); + + describe('analysis configuration page', () => { + const analysisConfig = new AnalysisConfigurationPage(); + + beforeAll(() => { + contextMenu.getMenuItems().then(items => { + items[2].link.click(); + }) + .then(() => browser.waitForAngular()) + .then(() => {}); + }); + + it('should have EAP 7 selected', (done) => { + analysisConfig.targets.EAP7.isSelected().then(selected => { + expect(selected).toBeTruthy(); + done(); + }); + }); + + it('should not have cloud readiness selected', (done) => { + analysisConfig.enableCloudReadinessInput.isSelected().then(selected => { + expect(selected).toBeFalsy(); + done(); + }); + }); + + it('should have all applications selected', (done) => { + analysisConfig.selectedApplications.count().then(count => { + expect(count).toBe(1); + done(); + }); + }); + + it('should not have any packages included', (done) => { + analysisConfig.includedPackages.count().then(count => { + expect(count).toBe(0); + done(); + }); + }); + + it('should not show exclude packages', () => { + + }); + + it('should not show custom rules', () => { + + }); + + it('should not show advanced options', () => { + + }); + }); +}); diff --git a/tests/e2e/src/main/npm/e2e/tests/project/run-analysis.e2e.ts b/tests/e2e/src/main/npm/e2e/tests/project/run-analysis.e2e.ts new file mode 100644 index 000000000..06ad1dd9b --- /dev/null +++ b/tests/e2e/src/main/npm/e2e/tests/project/run-analysis.e2e.ts @@ -0,0 +1,134 @@ +import {browser} from "protractor"; +import {CreateProjectWorkflow} from "../../workflows/create-project.wf"; +import {ContextMenuPage} from "../../pages/project-level/context-menu.po"; +import {AnalysisListPage, Execution} from "../../pages/project-level/analysis-list.po"; +import {AnalysisWorkflow} from "../../workflows/analysis.wf"; +import {waitUntilCondition} from "../../utils/async"; + +describe('Running analysis', () => { + const contextMenu = new ContextMenuPage(); + const analysisList = new AnalysisListPage(); + const analysisWorkflow = new AnalysisWorkflow(); + + let executions: Execution[]; + + beforeAll((done) => { + console.error('Creating project with analysis.....'); + browser.waitForAngular(); + /** + * Create project + */ + const workflow = new CreateProjectWorkflow(); + workflow.createProjectWithTimeInName() + .then(() => browser.waitForAngular()) + .then(() => console.error('Starting analysis....')) + .then(() => analysisWorkflow.startAnalysisFromProjectPage()) + .then(() => done()); + }); + + describe('running analysis', () => { + describe('analysis list page', () => { + beforeAll((done) => { + analysisList.getExecutions() + .then(executionList => executions = executionList) + .then(() => done()); + }); + + it('should contain active analysis in table', () => { + console.error(executions); + + const isQueued = executions[0].status.search('Queued') !== -1; + const isInProgress = executions[0].status.search('progress') !== -1; + + expect(executions.length).toBe(1); + expect(isQueued || isInProgress).toBeTruthy(); + }); + + it('should have blue table row for active analysis', (done) => { + executions[0].row.getAttribute('class').then(cssClass => { + expect(cssClass.search('info') !== -1).toBeTruthy(); + done(); + }); + }); + + it('should have analysis progress bar', (done) => { + console.error('Wait until analysis is queued.......'); + waitUntilCondition(200,() => { + return analysisList.getExecutions().then(executionList => { + return executionList.length == 1 && executionList[0].status.search('progress') !== -1; + }); + }, 3000) + .then(() => { + browser.waitForAngularEnabled(true); + return analysisList.getExecutions(); + }).then(executionList => { + executions = executionList; + }).then(() => analysisList.progressbar.isPresent()) + .then(isProgressbarPresent => { + expect(isProgressbarPresent).toBeTruthy(); + done(); + }); + }); + }); + }); + + describe('finished analysis', () => { + beforeAll((done) => { + /** + * TODO: Wait until analysis finishes + */ + let isAnalysisRunning = true; + + browser.waitForAngularEnabled(false); + + const waitForExecutionToFinish = (timeout: number, maxTimeout?: number, currentTimeout: number = 0): Promise => { + console.error('WaitForExecutionToFinish looping: ' + `timeout: ${timeout}, maxTimeout: ${maxTimeout}, current: ${currentTimeout}`); + + if (maxTimeout != 0 && currentTimeout >= maxTimeout) { + console.error('Max wait time exceeded, exiting'); + + return new Promise((resolve, reject) => { + reject('Max wait time exceeded'); + }); + } + + return browser.driver.sleep(timeout).then(() => analysisList.getExecutions() as any).then(executionsList => { + if (executionsList.length === 1 && executionsList[0].status.search('Completed') !== -1) { + isAnalysisRunning = false; + } else { + return waitForExecutionToFinish(timeout, maxTimeout, currentTimeout + timeout); + } + }); + }; + + console.error('Wait until analysis finishes.......'); + waitForExecutionToFinish(5 * 1000, 5 * 1000 * 60).then(() => { + console.error('Analysis is finished, continuing.......'); + browser.waitForAngularEnabled(true); + return analysisList.getExecutions(); + }).then(executionList => { + executions = executionList; + done(); + }); + }); + + it('should have completed status', () => { + expect(executions.length).toBe(1); + expect(executions[0].status.search('Completed') !== -1).toBeTruthy(); + }); + + it('should have report link', (done) => { + executions[0].actions.showReport.isPresent().then(hasReportLink => { + expect(hasReportLink).toBeTruthy(); + done(); + }); + }); + + it('should be green in table', (done) => { + executions[0].row.getAttribute('class').then(cssClass => { + expect(cssClass.search('success') !== -1).toBeTruthy(); + done(); + }); + }); + }); +}); diff --git a/tests/e2e/src/main/npm/e2e/utils/async.ts b/tests/e2e/src/main/npm/e2e/utils/async.ts new file mode 100644 index 000000000..46fd3d3a4 --- /dev/null +++ b/tests/e2e/src/main/npm/e2e/utils/async.ts @@ -0,0 +1,29 @@ +import {browser} from "protractor"; + +export function waitUntilCondition(timeout: number, condition: () => boolean|Promise, maxTimeout?: number, currentTimeout: number = 0): Promise { + let isLoopingTimeouts = true; + + browser.waitForAngularEnabled(false); + + const innerWaitUntilCondition = (timeout: number, maxTimeout?: number, currentTimeout: number = 0): Promise => { + console.error('waitUntilCondition looping: ' + `timeout: ${timeout}, maxTimeout: ${maxTimeout}, current: ${currentTimeout}`); + + if (maxTimeout != 0 && currentTimeout >= maxTimeout) { + console.error('Max wait time exceeded, exiting'); + + return new Promise((resolve, reject) => { + reject('Max wait time exceeded'); + }); + } + + return browser.driver.sleep(timeout).then(() => condition() as any).then(isConditionAccomplished => { + if (isConditionAccomplished) { + isLoopingTimeouts = false; + } else { + return innerWaitUntilCondition(timeout, maxTimeout, currentTimeout + timeout); + } + }); + }; + + return innerWaitUntilCondition(timeout, maxTimeout); +} diff --git a/tests/e2e/src/main/npm/e2e/utils/data.ts b/tests/e2e/src/main/npm/e2e/utils/data.ts new file mode 100644 index 000000000..92649c967 --- /dev/null +++ b/tests/e2e/src/main/npm/e2e/utils/data.ts @@ -0,0 +1,14 @@ +export const PROJECT_WITH_ANALYSIS = { + id: 1355, + name: 'Project with analysis', + executions: [ + { + id: 1 + } + ] +}; + +export const PROJECT_WITHOUT_ANALYSIS = { + id: 7905, + name: 'Project without analysis' +}; diff --git a/tests/e2e/src/main/npm/e2e/workflows/analysis.wf.ts b/tests/e2e/src/main/npm/e2e/workflows/analysis.wf.ts new file mode 100644 index 000000000..6f4df1a54 --- /dev/null +++ b/tests/e2e/src/main/npm/e2e/workflows/analysis.wf.ts @@ -0,0 +1,56 @@ +import {Project, ProjectListPage} from "../pages/project-list.po"; +import {ConfirmDialogPage} from "../pages/confirm-dialog.po"; +import {AnalysisConfigurationPage} from "../pages/project-level/analysis-config.po"; +import {$, browser} from "protractor"; +import {ContextMenuPage} from "../pages/project-level/context-menu.po"; + +export class AnalysisWorkflow { + + analysisConfigPage = new AnalysisConfigurationPage(); + contextMenuPage = new ContextMenuPage(); + confirmDialogPage = new ConfirmDialogPage(); + + startAnalysis(project: Project): Promise { + return project.projectDiv.click() + .then(() => browser.waitForAngular()) + .then(() => this.startAnalysisFromProjectPage()); + } + + startAnalysisFromProjectPage(): Promise { + return this.contextMenuPage.openAnalysisConfig() + .then(() => this.analysisConfigPage.saveAndRun()) + .then(() => browser.waitForAngular()) + .then(() => { + /** + * TODO: Here is some issue with timeout. isPresent() behaves very non-deterministically. + * Sometimes it resolves successfully, but sometimes it blocks and timeouts on 110 sec. internal protractor + * (or webdriver) timeout. + * + * I have no idea why. waitForAngularEnabled(false) solves that issue. + * + * Problem might be related to some setTimeout or setInterval async code. + */ + browser.waitForAngularEnabled(false); + console.error('Dialog present?'); + + return this.confirmDialogPage.isPresent().then(isPresent => { + console.error("Dialog present:", isPresent); + + if (isPresent) { + console.error('Modal dialog is showed, need to click confirm button'); + + return browser.isElementPresent(this.confirmDialogPage.confirmButton).then(isPresent => { + if (isPresent) { + console.error('Clicking on confirm button'); + return this.confirmDialogPage.clickConfirm(); + } + }); + } + }, error => { + console.error('error: ', error); + }).then(() => { + browser.waitForAngularEnabled(true); + }); + }); + } +} diff --git a/tests/e2e/src/main/npm/e2e/workflows/cleanup.wf.ts b/tests/e2e/src/main/npm/e2e/workflows/cleanup.wf.ts new file mode 100644 index 000000000..0c13453ef --- /dev/null +++ b/tests/e2e/src/main/npm/e2e/workflows/cleanup.wf.ts @@ -0,0 +1,49 @@ +import {Project, ProjectListPage} from "../pages/project-list.po"; +import {ConfirmDialogPage} from "../pages/confirm-dialog.po"; +import {browser} from "protractor"; + +export class CleanupWorkflow { + protected loadProject(name: string): Promise { + const projectPage = new ProjectListPage(); + + let projectList; + return projectPage.navigateTo().then(() => { + projectPage.getProjectList().then(projects => projectList = projects) + }).then(() => { + return projectList.find(item => item.name === name); + }); + } + + public deleteProjectFromList(name: string, projectList: Project[]) { + const project = projectList.find(item => item.name === name); + return this.deleteProject(project); + } + + public deleteProject(project: string|Project) { + if (typeof project === 'string') { + return this.loadProject(project) + .then(projectObject => this.doDeleteProject(projectObject)); + } else { + return this.doDeleteProject(project); + } + } + + protected doDeleteProject(project: Project): Promise { + if (project != null) { + return project.deleteButton.click().then(() => { + const confirmDialog = new ConfirmDialogPage(); + + confirmDialog.requiresText().then(requiresText => { + if (requiresText) { + confirmDialog.writeText(project.name); + } + + return confirmDialog.clickConfirm() + .then(() => browser.waitForAngular()); + }); + }); + } else { + return new Promise((resolve, reject) => reject('Project not found')); + } + } +} diff --git a/tests/e2e/src/main/npm/e2e/workflows/create-project.wf.ts b/tests/e2e/src/main/npm/e2e/workflows/create-project.wf.ts index b6ab81310..40fa128dc 100644 --- a/tests/e2e/src/main/npm/e2e/workflows/create-project.wf.ts +++ b/tests/e2e/src/main/npm/e2e/workflows/create-project.wf.ts @@ -1,20 +1,26 @@ -import {ProjectPage} from "../pages/project.po"; +import {ProjectListPage} from "../pages/project-list.po"; import {CreateProjectPage} from "../pages/wizard/create-project.po"; import {AddApplicationsPage} from "../pages/wizard/add-applications.po"; import {AnalysisConfigurationPage} from "../pages/wizard/analysis-configuration.po"; -import {browser, by, element} from "protractor"; +import {$, browser, by, element} from "protractor"; const UPLOAD_FILE_PATH = browser.params.upload.filePath; export class CreateProjectWorkflow { - public createProject(name: string) { - const projectPage = new ProjectPage(); + public createProject(name: string, description?: string) { + const projectPage = new ProjectListPage(); return projectPage.navigateTo() .then(() => projectPage.newProject()) .then(() => browser.waitForAngular()) .then(() => { const createProjectPage = new CreateProjectPage(); - return createProjectPage.setTitle(name).then(() => createProjectPage.clickNext()); + return createProjectPage.setTitle(name) + .then(() => { + if (description) { + createProjectPage.setDescription(description); + } + }) + .then(() => createProjectPage.clickNext()); }) .then(() => browser.waitForAngular()) .then(() => { @@ -28,8 +34,8 @@ export class CreateProjectWorkflow { }) .then(() => browser.waitForAngular()) .then(() => { - const confirmButton = element(by.css('.confirm-button')); - const modal = element(by.css('.modal')); + const confirmButton = $('.confirm-button'); + const modal = $('.modal'); return modal.isPresent().then(isPresent => { if (isPresent) { diff --git a/tests/e2e/src/main/npm/package.json b/tests/e2e/src/main/npm/package.json index 6fc8baa33..84a338b28 100644 --- a/tests/e2e/src/main/npm/package.json +++ b/tests/e2e/src/main/npm/package.json @@ -6,6 +6,7 @@ "e2e": "protractor", "e2e-local": "protractor --params.login.username test --params.login.password test --baseUrl http://localhost:8080/rhamt-web/ --params.baseUrl http://localhost:8080/rhamt-web/ --params.upload.filePath /home/dklingen/Downloads/mariadb-java-client-1.5.5.jar", "e2e-live": "protractor --elementExplorer", + "e2e-local-test-server": "protractor --params.upload.filePath /home/dklingen/Downloads/das-description-ws-2.0.war", "webdriver-update": "webdriver-manager update --gecko false", "webdriver-start": "webdriver-manager start", "postinstall": "npm run webdriver-update" @@ -19,6 +20,8 @@ "jasmine-spec-reporter": "^4.1.0", "protractor": "^5.1.2", "ts-node": "^3.0.6", - "typescript": "^2.3.2" + "typescript": "^2.3.2", + "xmldom": "^0.1.27", + "xpath": "0.0.24" } } diff --git a/tests/e2e/src/main/npm/protractor.conf.js b/tests/e2e/src/main/npm/protractor.conf.js index 531378664..af0dfadc4 100644 --- a/tests/e2e/src/main/npm/protractor.conf.js +++ b/tests/e2e/src/main/npm/protractor.conf.js @@ -3,8 +3,8 @@ const JUnitXmlReporter = require('jasmine-reporters').JUnitXmlReporter; const baseUrl = 'http://localhost:8180/rhamt-web/'; -function login() { - browser.driver.get(browser.params.baseUrl).then(function() { +function login(timeout, maxAttempts, attempts) { + return browser.driver.get(browser.params.baseUrl).then(function() { return browser.driver.getCurrentUrl(); }).then(function(url) { const pathRegex = /auth/; @@ -26,6 +26,19 @@ function login() { return !pathRegex.test(url); }); }, 10000); + } else { + console.error('Unexpected url: ', url); + + if (maxAttempts && attempts < maxAttempts) { + console.error('Retry login attempt: ', attempts); + return browser.driver.sleep(timeout).then(function() { + return login(timeout, maxAttempts, attempts + 1); + }); + } else { + console.error('Unable to log in in defined interval'); + process.exit(-1); + return false; + } } }); } @@ -58,7 +71,7 @@ exports.config = { capabilities: { 'browserName': 'chrome', 'chromeOptions': { - 'args': ['show-fps-counter=true', '--headless', '--disable-gpu', '--no-sandbox'] + 'args': ['show-fps-counter=true', '--no-sandbox'] } }, @@ -77,7 +90,7 @@ exports.config = { filePrefix: 'test-results.xml' })); - return login(); + return login(1000, 10, 0); }, // seleniumServerJar: "node_modules/protractor/selenium/selenium-server-standalone-2.52.0.jar", diff --git a/tests/e2e/src/main/npm/verify-results.js b/tests/e2e/src/main/npm/verify-results.js new file mode 100755 index 000000000..5f73a90bb --- /dev/null +++ b/tests/e2e/src/main/npm/verify-results.js @@ -0,0 +1,52 @@ +#!/usr/bin/env node + +const xpath = require('xpath'); +const DOMParser = require('xmldom').DOMParser; +const fs = require('fs'); +const path = require("path"); + +function makeDirIfNotExists(dirPath) { + if (!fs.existsSync(dirPath)) { + const parent = path.dirname(dirPath); + + makeDirIfNotExists(parent); + + fs.mkdirSync(dirPath); + } +} + +process.env.NODE_ENV = process.env.NODE_ENV || 'test'; + +if (process.argv.length < 4) { + console.error('Expecting and arguments'); + process.exit(1); +} + +const testResultsFile = process.argv[2]; +const failsafeResultsFile = process.argv[3]; + +if (!fs.existsSync(testResultsFile)) { + console.error('Test results file does not exist'); + process.exit(1); +} + +const testResultsXLM = fs.readFileSync(testResultsFile, 'utf8'); + +const document = new DOMParser().parseFromString(testResultsXLM, 'text/xml'); + +const countErrors = xpath.evaluate('count(//testcase/error)', document, null, xpath.XPathResult.NUMBER_TYPE, null).numberValue; +const countFailures = xpath.evaluate('count(//testcase/failure)', document, null, xpath.XPathResult.NUMBER_TYPE, null).numberValue; +const countSkipped = xpath.evaluate('count(//testcase/skipped)', document, null, xpath.XPathResult.NUMBER_TYPE, null).numberValue; +const countTestCases = xpath.evaluate('count(//testcase)', document, null, xpath.XPathResult.NUMBER_TYPE, null).numberValue; + +const failsafeSummary = "\n\ + " + countTestCases + "\n\ + " + countErrors + "\n\ + " + countFailures + "\n\ + " + countSkipped + "\n\ +\n"; + +makeDirIfNotExists(failsafeResultsFile); + +fs.writeFileSync(failsafeResultsFile, failsafeSummary); + diff --git a/tests/e2e/src/main/resources/h2/windup-web.h2.db b/tests/e2e/src/main/resources/h2/windup-web.h2.db new file mode 100644 index 000000000..5d6b091a0 Binary files /dev/null and b/tests/e2e/src/main/resources/h2/windup-web.h2.db differ diff --git a/tests/e2e/src/main/resources/windup/1355.zip b/tests/e2e/src/main/resources/windup/1355.zip new file mode 100644 index 000000000..ee861bfa5 Binary files /dev/null and b/tests/e2e/src/main/resources/windup/1355.zip differ diff --git a/tests/e2e/src/test/java/ITTestProtractorIT.java b/tests/e2e/src/test/java/ITTestProtractorIT.java new file mode 100644 index 000000000..213d2cf2a --- /dev/null +++ b/tests/e2e/src/test/java/ITTestProtractorIT.java @@ -0,0 +1,59 @@ +import org.junit.Assert; +import org.junit.Test; + +/** + * This test file is just workaround for maven-failsafe plugin + * + * It needs to have some tests to execute, so here is always passing test-suite. + * Real work is done in maven-exec plugin, which needs to run in integration-test phase. + * But if there are no tests, maven-failsafe plugin would stop maven before executing that. + */ +public class ITTestProtractorIT +{ + + @Test + public void passingTest() + { + Assert.assertTrue(true); + } +/* + @Test + public void protractorTest() + { + String workingDirectory = System.getProperty("workingDirectory"); + String protractorPath = System.getProperty("protractor.path"); + + System.setProperty("user.dir", workingDirectory); + + String[] protractorArgs = System.getProperty("protractor.args").split(" "); + String logFilePath = System.getProperty("logFile"); + + List protractorWithArgs = new ArrayList<>(); + protractorWithArgs.add(Paths.get(workingDirectory, protractorPath).toString()); + protractorWithArgs.addAll(Arrays.asList(protractorArgs)); + + Logger.getLogger(ITTestProtractorIT.class.getName()).warning("Protractor path: " + protractorPath); + + protractorWithArgs.forEach(arg -> Logger.getLogger(this.getClass().getName()).warning(arg)); + + ProcessBuilder processBuilder = new ProcessBuilder(protractorWithArgs); + processBuilder.redirectOutput(new File(logFilePath)); + + try + { + Process process = processBuilder.start(); + process.waitFor(); + Assert.assertEquals(0, process.exitValue()); + } + catch (IOException e) + { + e.printStackTrace(); + Assert.fail("IO Exception"); + } + catch (InterruptedException e) + { + e.printStackTrace(); + } + } +*/ +} diff --git a/ui/src/main/webapp/src/app/configuration/rules-modal.component.ts b/ui/src/main/webapp/src/app/configuration/rules-modal.component.ts index a98e8a980..363d024c9 100644 --- a/ui/src/main/webapp/src/app/configuration/rules-modal.component.ts +++ b/ui/src/main/webapp/src/app/configuration/rules-modal.component.ts @@ -1,6 +1,7 @@ -import {Component, Input, AfterViewInit} from "@angular/core"; +import {Component, Input, AfterViewInit, NgZone, OnDestroy} from "@angular/core"; import * as $ from "jquery"; import {RuleProviderEntity} from "../generated/windup-services"; +import {SchedulerService} from "../shared/scheduler.service"; declare function prettyPrint(); @@ -8,12 +9,24 @@ declare function prettyPrint(); selector: 'wu-rules-modal', templateUrl: 'rules-modal.component.html' }) -export class RulesModalComponent { +export class RulesModalComponent implements OnDestroy { @Input() ruleProviderEntity: RuleProviderEntity = {}; + prettyPrintTimeout: any; + + public constructor(private _schedulerService: SchedulerService, private _zone: NgZone) { + } + + ngOnDestroy(): void { + if (this.prettyPrintTimeout) { + this._schedulerService.clearTimeout(this.prettyPrintTimeout); + this.prettyPrintTimeout = null; + } + } + show(): void { ($('#rulesModal')).modal(); - setTimeout(() => prettyPrint(), 1000); + this.prettyPrintTimeout = this._schedulerService.setTimeout(() => prettyPrint(), 1000); } } diff --git a/ui/src/main/webapp/src/app/executions/active-executions-progressbar.component.html b/ui/src/main/webapp/src/app/executions/active-executions-progressbar.component.html index 989eca0fd..8282723a3 100644 --- a/ui/src/main/webapp/src/app/executions/active-executions-progressbar.component.html +++ b/ui/src/main/webapp/src/app/executions/active-executions-progressbar.component.html @@ -1,15 +1,15 @@

Active Analysis

-
- + + [maxValue]="mainExecution.totalWork" + [activeExecutionId]="mainExecution.id">
-
+
There is no active analysis.
diff --git a/ui/src/main/webapp/src/app/executions/execution-detail.component.ts b/ui/src/main/webapp/src/app/executions/execution-detail.component.ts index 062665ba0..2753b703c 100644 --- a/ui/src/main/webapp/src/app/executions/execution-detail.component.ts +++ b/ui/src/main/webapp/src/app/executions/execution-detail.component.ts @@ -1,4 +1,4 @@ -import {Component, OnDestroy, OnInit} from "@angular/core"; +import {Component, NgZone, OnDestroy, OnInit} from "@angular/core"; import {ActivatedRoute, Router} from "@angular/router"; import {WindupService} from "../services/windup.service"; import {WindupExecution, RegisteredApplication} from "../generated/windup-services"; @@ -12,6 +12,7 @@ import {RuleProviderExecutionsService} from "./rule-provider-executions/rule-pro import {ExecutionPhaseModel} from "../generated/tsModels/ExecutionPhaseModel"; import {RoutedComponent} from "../shared/routed.component"; import {RouteFlattenerService} from "../core/routing/route-flattener.service"; +import {SchedulerService} from "../shared/scheduler.service"; @Component({ templateUrl: './execution-detail.component.html', @@ -33,7 +34,9 @@ export class ExecutionDetailComponent extends RoutedComponent implements OnInit, _routeFlattener: RouteFlattenerService, private _eventBus: EventBusService, private _windupService: WindupService, - private _ruleProviderExecutionsService: RuleProviderExecutionsService + private _ruleProviderExecutionsService: RuleProviderExecutionsService, + private _schedulerService: SchedulerService, + private _zone: NgZone ) { super(_router, _activatedRoute, _routeFlattener); } @@ -60,16 +63,17 @@ export class ExecutionDetailComponent extends RoutedComponent implements OnInit, }); })); - this.currentTimeTimer = setInterval(() => { + this.currentTimeTimer = this._schedulerService.setInterval(this._zone.run(() => { this.currentTime = new Date().getTime(); console.log("Updating the current time field"); - }, 5000); + }), 5000); } ngOnDestroy(): void { super.ngOnDestroy(); - if (this.currentTimeTimer != null) - clearInterval(this.currentTimeTimer); + if (this.currentTimeTimer != null) { + this._schedulerService.clearInterval(this.currentTimeTimer); + } } get loglines(): Observable { diff --git a/ui/src/main/webapp/src/app/executions/executions-list.component.ts b/ui/src/main/webapp/src/app/executions/executions-list.component.ts index b9149eee2..134b3ad04 100644 --- a/ui/src/main/webapp/src/app/executions/executions-list.component.ts +++ b/ui/src/main/webapp/src/app/executions/executions-list.component.ts @@ -1,4 +1,4 @@ -import {Component, ElementRef, EventEmitter, Input, OnDestroy, OnInit, Output, ViewChild} from "@angular/core"; +import {Component, ElementRef, EventEmitter, Input, NgZone, OnDestroy, OnInit, Output, ViewChild} from "@angular/core"; import {WindupService} from "../services/windup.service"; import {MigrationProject, WindupExecution} from "../generated/windup-services"; import {NotificationService} from "../core/notification/notification.service"; @@ -8,6 +8,7 @@ import {MigrationProjectService} from "../project/migration-project.service"; import {WindupExecutionService} from "../services/windup-execution.service"; import {ConfirmationModalComponent} from "../shared/dialog/confirmation-modal.component"; import {AbstractComponent} from "../shared/AbstractComponent"; +import {SchedulerService} from "../shared/scheduler.service"; @Component({ selector: 'wu-executions-list', @@ -53,7 +54,9 @@ export class ExecutionsListComponent extends AbstractComponent implements OnInit private _windupService: WindupService, private _notificationService: NotificationService, private _sortingService: SortingService, - private _projectService: MigrationProjectService + private _projectService: MigrationProjectService, + private _schedulerService: SchedulerService, + private _zone: NgZone ) { super(); this.element = _elementRef.nativeElement; @@ -72,14 +75,17 @@ export class ExecutionsListComponent extends AbstractComponent implements OnInit this.doDeleteExecution(execution); }); - this.currentTimeTimer = setInterval(() => { - this.currentTime = new Date().getTime(); + this.currentTimeTimer = this._schedulerService.setInterval(() => { + this._zone.run(() => { + this.currentTime = new Date().getTime(); + }); }, 5000); } ngOnDestroy(): void { - if (this.currentTimeTimer) - clearInterval(this.currentTimeTimer); + if (this.currentTimeTimer) { + this._schedulerService.clearInterval(this.currentTimeTimer); + } } @Input() diff --git a/ui/src/main/webapp/src/app/executions/project-executions.component.ts b/ui/src/main/webapp/src/app/executions/project-executions.component.ts index 8b9fa12a8..07dc59ac1 100644 --- a/ui/src/main/webapp/src/app/executions/project-executions.component.ts +++ b/ui/src/main/webapp/src/app/executions/project-executions.component.ts @@ -1,4 +1,4 @@ -import {Component, OnInit} from "@angular/core"; +import {Component, NgZone, OnDestroy, OnInit} from "@angular/core"; import {AnalysisContext, MigrationProject, WindupExecution} from "../generated/windup-services"; import {RegisteredApplication} from "../generated/windup-services"; import {ActivatedRoute} from "@angular/router"; @@ -10,16 +10,18 @@ import {ExecutionEvent} from "../core/events/windup-event"; import {AnalysisContextService} from "../analysis-context/analysis-context.service"; import {NotificationService} from "../core/notification/notification.service"; import {utils} from "../shared/utils"; +import {SchedulerService} from "../shared/scheduler.service"; @Component({ templateUrl: './project-executions.component.html' }) -export class ProjectExecutionsComponent extends ExecutionsMonitoringComponent implements OnInit { +export class ProjectExecutionsComponent extends ExecutionsMonitoringComponent implements OnInit, OnDestroy { executions: WindupExecution[]; private doNotRefreshList: boolean; private analysisContext: AnalysisContext; protected showRunAnalysisButton: boolean; + protected refreshTimeout: any; constructor( private _activatedRoute: ActivatedRoute, @@ -27,7 +29,9 @@ export class ProjectExecutionsComponent extends ExecutionsMonitoringComponent im private _eventBus: EventBusService, private _windupService: WindupService, private _analysisContextService: AnalysisContextService, - private _notificationService: NotificationService + private _notificationService: NotificationService, + private _schedulerService: SchedulerService, + private _ngZone: NgZone ) { super(_windupExecutionService); } @@ -56,13 +60,23 @@ export class ProjectExecutionsComponent extends ExecutionsMonitoringComponent im this.doNotRefreshList = false; } + ngOnDestroy(): void { + super.ngOnDestroy(); + + if (this.refreshTimeout) { + this._schedulerService.clearTimeout(this.refreshTimeout); + this.refreshTimeout = null; + } + } + refreshExecutionList() { this._windupService.getProjectExecutions(this.project.id).subscribe(executions => { this.executions = executions; // If there are cancelled jobs that have not yet had a cancelled date added, then refresh the list - if (this.executions.find(execution => execution.state == "CANCELLED" && execution.timeCompleted == null) != null) - setTimeout(() => this.refreshExecutionList(), 1000); + if (this.executions.find(execution => execution.state == "CANCELLED" && execution.timeCompleted == null) != null) { + this.refreshTimeout = this._schedulerService.setTimeout(this._ngZone.run(() => this.refreshExecutionList()), 1000); + } this.loadActiveExecutions(this.executions); }); diff --git a/ui/src/main/webapp/src/app/reports/migration-issues/migration-issues-table.component.ts b/ui/src/main/webapp/src/app/reports/migration-issues/migration-issues-table.component.ts index 3d9cded85..3bd816f22 100644 --- a/ui/src/main/webapp/src/app/reports/migration-issues/migration-issues-table.component.ts +++ b/ui/src/main/webapp/src/app/reports/migration-issues/migration-issues-table.component.ts @@ -1,4 +1,4 @@ -import {ChangeDetectionStrategy, Component, Input, OnInit} from "@angular/core"; +import {ChangeDetectionStrategy, Component, Input, NgZone, OnInit} from "@angular/core"; import {Router, ActivatedRoute} from "@angular/router"; import "../source/prism"; @@ -7,6 +7,7 @@ import {NotificationService} from "../../core/notification/notification.service" import {SortingService, OrderDirection} from "../../shared/sort/sorting.service"; import {RouteFlattenerService} from "../../core/routing/route-flattener.service"; import {FilterableReportComponent} from "../filterable-report.component"; +import {SchedulerService} from "../../shared/scheduler.service"; @Component({ selector: 'wu-migration-issues-table', @@ -32,7 +33,8 @@ export class MigrationIssuesTableComponent extends FilterableReportComponent imp _activatedRoute: ActivatedRoute, private _migrationIssuesService: MigrationIssuesService, private _notificationService: NotificationService, - private _sortingService: SortingService + private _sortingService: SortingService, + private _schedulerService: SchedulerService ) { super(_router, _activatedRoute, _routeFlattener); } @@ -76,7 +78,7 @@ export class MigrationIssuesTableComponent extends FilterableReportComponent imp private delayedPrismRender() { // Colorize the included code snippets on the first displaying. - setTimeout(() => Prism.highlightAll(false), 1000); + this._schedulerService.setTimeout(() => Prism.highlightAll(false), 1000); } toggleFiles(summary: ProblemSummary) { diff --git a/ui/src/main/webapp/src/app/reports/migration-issues/problem-summary-files.component.ts b/ui/src/main/webapp/src/app/reports/migration-issues/problem-summary-files.component.ts index 98d216847..2289a4bb8 100644 --- a/ui/src/main/webapp/src/app/reports/migration-issues/problem-summary-files.component.ts +++ b/ui/src/main/webapp/src/app/reports/migration-issues/problem-summary-files.component.ts @@ -1,17 +1,18 @@ -import {Component, Input, OnInit} from "@angular/core"; +import {Component, Input, NgZone, OnDestroy, OnInit} from "@angular/core"; import {FileModel} from "../../generated/tsModels/FileModel"; import {ActivatedRoute, Router} from "@angular/router"; import {GraphJSONToModelService} from "../../services/graph/graph-json-to-model.service"; import {PaginationService} from "../../shared/pagination.service"; import * as showdown from "showdown"; +import {SchedulerService} from "../../shared/scheduler.service"; @Component({ selector: 'wu-problem-summary-files', templateUrl: './problem-summary-files.component.html', styleUrls: ['./problem-summary-files.component.scss'] }) -export class ProblemSummaryFilesComponent implements OnInit { +export class ProblemSummaryFilesComponent implements OnInit, OnDestroy { _problemSummaryFiles: any[]; @Input() @@ -29,11 +30,14 @@ export class ProblemSummaryFilesComponent implements OnInit { private markdownCache: Map = new Map(); + private renderTimeout; + public constructor( private _router: Router, private _activatedRoute: ActivatedRoute, private _graphJsonToModelService: GraphJSONToModelService, - private _paginationService: PaginationService + private _paginationService: PaginationService, + private _schedulerService: SchedulerService ) { } @@ -52,6 +56,13 @@ export class ProblemSummaryFilesComponent implements OnInit { this.parseExecutedRulesPath(); } + ngOnDestroy(): void { + if (this.renderTimeout) { + this._schedulerService.clearTimeout(this.renderTimeout); + this.renderTimeout = null; + } + } + protected parseExecutedRulesPath() { let currentUrl = this._activatedRoute.snapshot.pathFromRoot.reduce((accumulator, item) => { let currentPart = item.url.reduce((acc, itm) => { @@ -86,7 +97,7 @@ export class ProblemSummaryFilesComponent implements OnInit { private delayedPrismRender() { const timeout = 60 * 1000; // Colorize the included code snippets on the first displaying. - setTimeout(() => Prism.highlightAll(false), timeout); + this.renderTimeout = this._schedulerService.setTimeout(() => Prism.highlightAll(false), timeout); } renderMarkdownToHtml(markdownCode: string): string { diff --git a/ui/src/main/webapp/src/app/shared/js-tree-angular-wrapper.component.ts b/ui/src/main/webapp/src/app/shared/js-tree-angular-wrapper.component.ts index 4b73efab4..8a3a16edb 100644 --- a/ui/src/main/webapp/src/app/shared/js-tree-angular-wrapper.component.ts +++ b/ui/src/main/webapp/src/app/shared/js-tree-angular-wrapper.component.ts @@ -1,10 +1,11 @@ import { Component, OnInit, Input, ElementRef, SimpleChange, Output, EventEmitter, NgZone, - OnChanges + OnChanges, OnDestroy } from "@angular/core"; import {Package} from "../generated/windup-services"; import * as $ from "jquery"; import 'jstree'; +import {SchedulerService} from "./scheduler.service"; /** * Wrapper for jstree from: https://www.jstree.com/ @@ -14,7 +15,7 @@ import 'jstree'; selector: 'wu-js-tree-wrapper', host: { 'style': 'display: block; overflow: auto;' } }) -export class JsTreeAngularWrapperComponent implements OnInit, OnChanges { +export class JsTreeAngularWrapperComponent implements OnInit, OnChanges, OnDestroy { @Input() treeNodes: TreeData[]; @@ -39,7 +40,9 @@ export class JsTreeAngularWrapperComponent implements OnInit, OnChanges { protected updateSelectionCallback: Function = () => {}; protected static EMPTY_CALLBACK = () => {}; - public constructor(element: ElementRef, private _zone: NgZone) { + protected treeRedrawTimeout: any; + + public constructor(element: ElementRef, private _zone: NgZone, private _schedulerService: SchedulerService) { this.element = element.nativeElement; } @@ -60,7 +63,7 @@ export class JsTreeAngularWrapperComponent implements OnInit, OnChanges { if (changes.hasOwnProperty('selectedNodes')) { // Another ugly workaround, now to give enough time to initialize jsTree first - setTimeout(() => this.redrawSelection(), 100); + this._schedulerService.setTimeout(this._zone.run(() => this.redrawSelection()), 100); } } @@ -121,6 +124,13 @@ export class JsTreeAngularWrapperComponent implements OnInit, OnChanges { $(this.element).on('changed.jstree loaded.jstree', (event, data) => this.redrawSelection()); } + ngOnDestroy(): void { + if (this.treeRedrawTimeout) { + this._schedulerService.clearTimeout(this.treeRedrawTimeout); + this.treeRedrawTimeout = null; + } + } + fireNodeClicked(event, data) { this.nodeClicked.emit(this.treeNodesMap[data.node.id]); } diff --git a/ui/src/main/webapp/src/app/shared/notification.component.ts b/ui/src/main/webapp/src/app/shared/notification.component.ts index 93c2b14ac..72d742c54 100644 --- a/ui/src/main/webapp/src/app/shared/notification.component.ts +++ b/ui/src/main/webapp/src/app/shared/notification.component.ts @@ -1,7 +1,8 @@ -import {Component, OnDestroy, OnInit, Input} from "@angular/core"; +import {Component, OnDestroy, OnInit, Input, NgZone} from "@angular/core"; import {NotificationService} from "../core/notification/notification.service"; import {Subscription} from "rxjs/Subscription"; import {Notification, NotificationLevel} from "../core/notification/notification"; +import {SchedulerService} from "./scheduler.service"; @Component({ selector: 'wu-notification', @@ -21,7 +22,13 @@ export class NotificationComponent implements OnInit, OnDestroy { notificationsStack: Notification[] = []; - constructor(private _notificationService: NotificationService) { + protected closeTimeoutHandle: any; + + constructor( + private _notificationService: NotificationService, + private _schedulerService: SchedulerService, + private _zone: NgZone + ) { } @@ -37,7 +44,10 @@ export class NotificationComponent implements OnInit, OnDestroy { this.notificationsStack.push(notification); if (this.autoCloseNotifications) { - setTimeout(() => this.closeNotification(notification), this.closeTimeout * 1000); + this.closeTimeoutHandle = this._schedulerService.setTimeout( + this._zone.run(() => this.closeNotification(notification)), + this.closeTimeout * 1000 + ); } } @@ -92,5 +102,10 @@ export class NotificationComponent implements OnInit, OnDestroy { ngOnDestroy(): any { this.subscription.unsubscribe(); + + if (this.closeTimeoutHandle) { + this._schedulerService.clearTimeout(this.closeTimeoutHandle); + this.closeTimeoutHandle = null; + } } } diff --git a/ui/src/main/webapp/tests/app/components/reports/migration-issues/migration-issues.component.spec.ts b/ui/src/main/webapp/tests/app/components/reports/migration-issues/migration-issues.component.spec.ts index 0801d9e30..1abdd4cb0 100644 --- a/ui/src/main/webapp/tests/app/components/reports/migration-issues/migration-issues.component.spec.ts +++ b/ui/src/main/webapp/tests/app/components/reports/migration-issues/migration-issues.component.spec.ts @@ -23,6 +23,7 @@ import {PaginationComponent} from "../../../../../src/app/shared/pagination.comp import {SearchComponent} from "../../../../../src/app/shared/search/search.component"; import {AllDataFilteredMessageComponent} from "../../../../../src/app/shared/all-data-filtered-message.component"; import {FormsModule} from "@angular/forms"; +import {SchedulerService} from "../../../../../src/app/shared/scheduler.service"; let comp: MigrationIssuesComponent; let fixture: ComponentFixture; @@ -51,6 +52,7 @@ describe('MigrationissuesComponent', () => { provide: ActivatedRoute, useValue: activatedRouteMock }, + SchedulerService, PaginationService, RouteFlattenerService, MockBackend,