diff --git a/.github/workflows/pull-request.yml b/.github/workflows/pull-request.yml index 937194abd..20f7d8138 100644 --- a/.github/workflows/pull-request.yml +++ b/.github/workflows/pull-request.yml @@ -180,6 +180,8 @@ jobs: id: 3 - name: "authentication" id: 4 + - name: "navigation" + id: 5 steps: - name: Checkout uses: actions/checkout@v3 diff --git a/e2e/playwright/navigation/.eslintrc.json b/e2e/playwright/navigation/.eslintrc.json new file mode 100644 index 000000000..0e3d5cf2e --- /dev/null +++ b/e2e/playwright/navigation/.eslintrc.json @@ -0,0 +1,26 @@ +{ + "extends": "../../../.eslintrc.json", + "ignorePatterns": [ + "!**/*" + ], + "overrides": [ + { + "files": [ + "*.ts" + ], + "parserOptions": { + "project": [ + "e2e/playwright/authentication/tsconfig.e2e.json" + ], + "createDefaultProgram": true + }, + "plugins": [ + "rxjs", + "unicorn" + ], + "rules": { + "@typescript-eslint/no-floating-promises": "off" + } + } + ] +} diff --git a/e2e/playwright/navigation/exclude.tests.json b/e2e/playwright/navigation/exclude.tests.json new file mode 100644 index 000000000..0967ef424 --- /dev/null +++ b/e2e/playwright/navigation/exclude.tests.json @@ -0,0 +1 @@ +{} diff --git a/e2e/playwright/navigation/playwright.config.ts b/e2e/playwright/navigation/playwright.config.ts new file mode 100644 index 000000000..c1851da8a --- /dev/null +++ b/e2e/playwright/navigation/playwright.config.ts @@ -0,0 +1,44 @@ +/*! + * Copyright © 2005-2023 Hyland Software, Inc. and its affiliates. All rights reserved. + * + * Alfresco Example Content Application + * + * This file is part of the Alfresco Example Content Application. + * If the software was purchased under a paid Alfresco license, the terms of + * the paid license agreement will prevail. Otherwise, the software is + * provided under the following open source license terms: + * + * The Alfresco Example Content Application is free software: you can redistribute it and/or modify + * it under the terms of the GNU Lesser General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * The Alfresco Example Content Application is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * from Hyland Software. If not, see . + */ + +import { PlaywrightTestConfig } from '@playwright/test'; +import { CustomConfig, getGlobalConfig, getExcludedTestsRegExpArray } from '@alfresco/playwright-shared'; +import EXCLUDED_JSON from './exclude.tests.json'; + +const config: PlaywrightTestConfig = { + ...getGlobalConfig, + + grepInvert: getExcludedTestsRegExpArray(EXCLUDED_JSON, 'Navigation'), + projects: [ + { + name: 'Navigation', + testDir: './src/tests', + use: { + users: ['hruser', 'admin'] + } + } + ] +}; + +export default config; diff --git a/e2e/playwright/navigation/project.json b/e2e/playwright/navigation/project.json new file mode 100644 index 000000000..8dde0c968 --- /dev/null +++ b/e2e/playwright/navigation/project.json @@ -0,0 +1,34 @@ +{ + "$schema": "../../../node_modules/nx/schemas/project-schema.json", + "name": "navigation-e2e", + "sourceRoot": "e2e/playwright/navigation/src", + "projectType": "application", + "targets": { + "e2e": { + "executor": "nx:run-commands", + "options": { + "commands": [ + "npx playwright test --config=e2e/playwright/navigation/playwright.config.ts" + ] + }, + "configurations": { + "production": { + "devServerTarget": "content-ce:serve:production" + } + } + }, + "lint": { + "executor": "@angular-eslint/builder:lint", + "options": { + "lintFilePatterns": [ + "e2e/**/*.ts", + "e2e/**/*.html" + ], + "cache": true, + "cacheLocation": ".eslintcache", + "ignorePath": ".eslintignore" + }, + "outputs": ["{options.outputFile}"] + } + } +} diff --git a/e2e/playwright/navigation/src/tests/breadcrumb.spec.ts b/e2e/playwright/navigation/src/tests/breadcrumb.spec.ts new file mode 100644 index 000000000..c7983a417 --- /dev/null +++ b/e2e/playwright/navigation/src/tests/breadcrumb.spec.ts @@ -0,0 +1,128 @@ +/*! + * Copyright © 2005-2023 Hyland Software, Inc. and its affiliates. All rights reserved. + * + * Alfresco Example Content Application + * + * This file is part of the Alfresco Example Content Application. + * If the software was purchased under a paid Alfresco license, the terms of + * the paid license agreement will prevail. Otherwise, the software is + * provided under the following open source license terms: + * + * The Alfresco Example Content Application is free software: you can redistribute it and/or modify + * it under the terms of the GNU Lesser General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * The Alfresco Example Content Application is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * from Hyland Software. If not, see . + */ + +import { expect } from '@playwright/test'; +import { ApiClientFactory, getUserState, SITE_VISIBILITY, test, Utils } from '@alfresco/playwright-shared'; + +test.use({ storageState: getUserState('hruser') }); +test.describe('viewer action file', () => { + const apiClientFactory = new ApiClientFactory(); + + const parent = `parent-${Utils.random()}`; + let parentId: string; + const subFolder1 = `subFolder1-${Utils.random()}`; + let subFolder1Id: string; + const subFolder2 = `subFolder2-${Utils.random()}`; + let subFolder2Id: string; + const fileName1 = `file1-${Utils.random()}.txt`; + + const siteName = `site-${Utils.random()}`; + const parentFromSite = `parent-in-site-${Utils.random()}`; + let parentFromSiteId: string; + const subFolder1FromSite = `subFolder1-in-site-${Utils.random()}`; + let subFolder1FromSiteId: string; + const subFolder2FromSite = `subFolder2-in-site-${Utils.random()}`; + let subFolder2FromSiteId: string; + const fileName1FromSite = `file1-in-site-${Utils.random()}.txt`; + + const parent2 = `parent2-${Utils.random()}`; + let parent2Id: string; + const folder1 = `folder1-${Utils.random()}`; + let folder1Id: string; + const folder1Renamed = `renamed-${Utils.random()}`; + + test.beforeAll(async ({ nodesApiAction, sitesApiAction }) => { + await apiClientFactory.setUpAcaBackend('hruser'); + + const parentNode = await nodesApiAction.createFolder(parent); + parentId = parentNode.entry.id; + subFolder1Id = (await nodesApiAction.createFolder(subFolder1, parentId)).entry.id; + subFolder2Id = (await nodesApiAction.createFolder(subFolder2, subFolder1Id)).entry.id; + await nodesApiAction.createFile(fileName1, subFolder2Id); + + parent2Id = (await nodesApiAction.createFolder(parent2)).entry.id; + folder1Id = (await nodesApiAction.createFolder(folder1, parent2Id)).entry.id; + + await sitesApiAction.createSite(siteName, SITE_VISIBILITY.PUBLIC); + const docLibId = await sitesApiAction.getDocLibId(siteName); + parentFromSiteId = (await nodesApiAction.createFolder(parentFromSite, docLibId)).entry.id; + subFolder1FromSiteId = (await nodesApiAction.createFolder(subFolder1FromSite, parentFromSiteId)).entry.id; + subFolder2FromSiteId = (await nodesApiAction.createFolder(subFolder2FromSite, subFolder1FromSiteId)).entry.id; + await nodesApiAction.createFile(fileName1FromSite, subFolder2FromSiteId); + }); + + test.afterAll(async () => { + await apiClientFactory.nodes.deleteNode(parentId, { permanent: true }); + }); + + test('[C260964] Personal Files breadcrumb main node', async ({ personalFiles }) => { + await personalFiles.navigate({ remoteUrl: `#/personal-files/` }); + expect(await personalFiles.breadcrumb.items.count(), 'Breadcrumb has incorrect number of items').toEqual(1); + expect(await personalFiles.breadcrumb.currentItem.innerText()).toBe('Personal Files'); + }); + + test('[C260965] Personal Files breadcrumb for a folder hierarchy', async ({ personalFiles }) => { + await personalFiles.navigate({ remoteUrl: `#/personal-files/${subFolder2Id}` }); + const expectedBreadcrumb = ['Personal Files', parent, subFolder1, subFolder2]; + expect(await personalFiles.breadcrumb.getAllItems()).toEqual(expectedBreadcrumb); + }); + + test('[C260967] File Libraries breadcrumb for a folder hierarchy', async ({ myLibrariesPage }) => { + await myLibrariesPage.navigate({ remoteUrl: `#/libraries/${subFolder2FromSiteId}` }); + const expectedItems = ['My Libraries', siteName, parentFromSite, subFolder1FromSite, subFolder2FromSite]; + expect(await myLibrariesPage.breadcrumb.getAllItems()).toEqual(expectedItems); + }); + + test('[C213235] User can navigate to any location by clicking on a step from the breadcrumb', async ({ personalFiles }) => { + await personalFiles.navigate({ remoteUrl: `#/personal-files/${subFolder2Id}` }); + await personalFiles.breadcrumb.clickItem(subFolder1); + await personalFiles.dataTable.spinnerWaitForReload(); + const expectedBreadcrumb = ['Personal Files', parent, subFolder1]; + expect(await personalFiles.breadcrumb.getAllItems()).toEqual(expectedBreadcrumb); + }); + + test('[C213237] Tooltip appears on hover on a step in breadcrumb', async ({ personalFiles }) => { + await personalFiles.navigate({ remoteUrl: `#/personal-files/${subFolder2Id}` }); + const item = personalFiles.breadcrumb.items.nth(2); + const title = await item.getAttribute('title'); + expect(title).toEqual(subFolder1); + }); + + test('[C213238] Breadcrumb updates correctly when folder is renamed', async ({ personalFiles, nodesApiAction }) => { + await nodesApiAction.renameNode(folder1Id, folder1Renamed); + await personalFiles.navigate({ remoteUrl: `#/personal-files/${folder1Id}` }); + await personalFiles.page.reload(); + await personalFiles.dataTable.spinnerWaitForReload(); + expect(await personalFiles.breadcrumb.currentItem.innerText()).toEqual(folder1Renamed); + }); + + test('[C213240] Browser back navigates to previous location regardless of breadcrumb steps', async ({ personalFiles, trashPage }) => { + await personalFiles.navigate({ remoteUrl: `#/personal-files/${subFolder2Id}` }); + await trashPage.navigate(); + await personalFiles.page.goBack(); + await personalFiles.dataTable.spinnerWaitForReload(); + const expectedBreadcrumb = ['Personal Files', parent, subFolder1, subFolder2]; + expect(await personalFiles.breadcrumb.getAllItems()).toEqual(expectedBreadcrumb); + }); +}); diff --git a/e2e/playwright/navigation/tsconfig.e2e.adf.json b/e2e/playwright/navigation/tsconfig.e2e.adf.json new file mode 100644 index 000000000..87cbcf775 --- /dev/null +++ b/e2e/playwright/navigation/tsconfig.e2e.adf.json @@ -0,0 +1,15 @@ +{ + "extends": "../../../tsconfig.adf.json", + "compilerOptions": { + "outDir": "../../out-tsc/e2e", + "baseUrl": "./", + "module": "commonjs", + "target": "es2017", + "types": ["jasmine", "jasminewd2", "node"], + "skipLibCheck": true, + "paths": { + "@alfresco/playwright-shared": ["../../../projects/aca-playwright-shared/src/index.ts"] + } + }, + "exclude": ["node_modules"] +} diff --git a/e2e/playwright/navigation/tsconfig.e2e.json b/e2e/playwright/navigation/tsconfig.e2e.json new file mode 100755 index 000000000..d1d140fba --- /dev/null +++ b/e2e/playwright/navigation/tsconfig.e2e.json @@ -0,0 +1,12 @@ +{ + "extends": "../../../tsconfig.json", + "compilerOptions": { + "outDir": "../../out-tsc/e2e", + "baseUrl": "./", + "module": "commonjs", + "target": "es2017", + "types": ["jasmine", "jasminewd2", "node", "@playwright/test"], + "skipLibCheck": true, + }, + "exclude": ["node_modules"] +} diff --git a/e2e/protractor/suites/navigation/breadcrumb.test.ts b/e2e/protractor/suites/navigation/breadcrumb.test.ts deleted file mode 100755 index 5e06a1231..000000000 --- a/e2e/protractor/suites/navigation/breadcrumb.test.ts +++ /dev/null @@ -1,193 +0,0 @@ -/*! - * Copyright © 2005-2023 Hyland Software, Inc. and its affiliates. All rights reserved. - * - * Alfresco Example Content Application - * - * This file is part of the Alfresco Example Content Application. - * If the software was purchased under a paid Alfresco license, the terms of - * the paid license agreement will prevail. Otherwise, the software is - * provided under the following open source license terms: - * - * The Alfresco Example Content Application is free software: you can redistribute it and/or modify - * it under the terms of the GNU Lesser General Public License as published by - * the Free Software Foundation, either version 3 of the License, or - * (at your option) any later version. - * - * The Alfresco Example Content Application is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU Lesser General Public License for more details. - * - * You should have received a copy of the GNU Lesser General Public License - * from Hyland Software. If not, see . - */ - -import { browser } from 'protractor'; - -import { AdminActions, SITE_VISIBILITY, LoginPage, BrowsingPage, Utils, RepoClient } from '@alfresco/aca-testing-shared'; -import { BrowserActions } from '@alfresco/adf-testing'; - -describe('Breadcrumb', () => { - const username = `user-${Utils.random()}`; - - const parent = `parent-${Utils.random()}`; - let parentId: string; - const subFolder1 = `subFolder1-${Utils.random()}`; - let subFolder1Id: string; - const subFolder2 = `subFolder2-${Utils.random()}`; - let subFolder2Id: string; - const fileName1 = `file1-${Utils.random()}.txt`; - - const siteName = `site-${Utils.random()}`; - const parentFromSite = `parent-in-site-${Utils.random()}`; - let parentFromSiteId: string; - const subFolder1FromSite = `subFolder1-in-site-${Utils.random()}`; - let subFolder1FromSiteId: string; - const subFolder2FromSite = `subFolder2-in-site-${Utils.random()}`; - let subFolder2FromSiteId: string; - const fileName1FromSite = `file1-in-site-${Utils.random()}.txt`; - - const parent2 = `parent2-${Utils.random()}`; - let parent2Id: string; - const folder1 = `folder1-${Utils.random()}`; - let folder1Id: string; - const folder1Renamed = `renamed-${Utils.random()}`; - - const loginPage = new LoginPage(); - const page = new BrowsingPage(); - const { breadcrumb } = page; - - const apis = { - user: new RepoClient(username, username) - }; - const adminApiActions = new AdminActions(); - - beforeAll(async () => { - await adminApiActions.createUser({ username }); - parentId = (await apis.user.nodes.createFolder(parent)).entry.id; - subFolder1Id = (await apis.user.nodes.createFolder(subFolder1, parentId)).entry.id; - subFolder2Id = (await apis.user.nodes.createFolder(subFolder2, subFolder1Id)).entry.id; - await apis.user.nodes.createFile(fileName1, subFolder2Id); - - parent2Id = (await apis.user.nodes.createFolder(parent2)).entry.id; - folder1Id = (await apis.user.nodes.createFolder(folder1, parent2Id)).entry.id; - - await apis.user.sites.createSite(siteName, SITE_VISIBILITY.PUBLIC); - const docLibId = await apis.user.sites.getDocLibId(siteName); - parentFromSiteId = (await apis.user.nodes.createFolder(parentFromSite, docLibId)).entry.id; - subFolder1FromSiteId = (await apis.user.nodes.createFolder(subFolder1FromSite, parentFromSiteId)).entry.id; - subFolder2FromSiteId = (await apis.user.nodes.createFolder(subFolder2FromSite, subFolder1FromSiteId)).entry.id; - await apis.user.nodes.createFile(fileName1FromSite, subFolder2FromSiteId); - - await loginPage.loginWith(username); - }); - - afterAll(async () => { - await Promise.all([apis.user.nodes.deleteNodeById(parentId), apis.user.nodes.deleteNodeById(parent2Id), apis.user.sites.deleteSite(siteName)]); - }); - - async function verifyBreadcrumb(expectedCount: number, expectedText: string) { - expect(await breadcrumb.items.count()).toEqual(expectedCount, 'Breadcrumb has incorrect number of items'); - expect(await breadcrumb.currentItem.getText()).toBe(expectedText); - } - - it('[C260964] Personal Files breadcrumb main node', async () => { - await page.clickPersonalFiles(); - await verifyBreadcrumb(1, 'Personal Files'); - }); - - it('[C260965] Personal Files breadcrumb for a folder hierarchy', async () => { - await page.clickPersonalFilesAndWait(); - await page.dataTable.doubleClickOnRowByName(parent); - await page.dataTable.doubleClickOnRowByName(subFolder1); - await page.dataTable.doubleClickOnRowByName(subFolder2); - const expectedBreadcrumb = ['Personal Files', parent, subFolder1, subFolder2]; - expect(await breadcrumb.getAllItems()).toEqual(expectedBreadcrumb); - }); - - it('[C260967] File Libraries breadcrumb for a folder hierarchy', async () => { - await page.goToMyLibrariesAndWait(); - await page.dataTable.doubleClickOnRowByName(siteName); - await page.dataTable.doubleClickOnRowByName(parentFromSite); - await page.dataTable.doubleClickOnRowByName(subFolder1FromSite); - await page.dataTable.doubleClickOnRowByName(subFolder2FromSite); - const expectedItems = ['My Libraries', siteName, parentFromSite, subFolder1FromSite, subFolder2FromSite]; - expect(await breadcrumb.getAllItems()).toEqual(expectedItems); - }); - - it('[C213235] User can navigate to any location by clicking on a step from the breadcrumb', async () => { - await page.clickPersonalFilesAndWait(); - await page.dataTable.doubleClickOnRowByName(parent); - await page.dataTable.doubleClickOnRowByName(subFolder1); - await page.dataTable.doubleClickOnRowByName(subFolder2); - await breadcrumb.clickItem(subFolder1); - const expectedBreadcrumb = ['Personal Files', parent, subFolder1]; - expect(await breadcrumb.getAllItems()).toEqual(expectedBreadcrumb); - }); - - it('[C213237] Tooltip appears on hover on a step in breadcrumb', async () => { - await page.clickPersonalFilesAndWait(); - await page.dataTable.doubleClickOnRowByName(parent); - await page.dataTable.doubleClickOnRowByName(subFolder1); - await page.dataTable.doubleClickOnRowByName(subFolder2); - - const item = breadcrumb.items.get(2); - const title = await BrowserActions.getAttribute(item, 'title'); - - expect(title).toEqual(subFolder1); - }); - - it('[C213238] Breadcrumb updates correctly when folder is renamed', async () => { - await page.clickPersonalFilesAndWait(); - await page.dataTable.doubleClickOnRowByName(parent2); - await page.dataTable.doubleClickOnRowByName(folder1); - await page.dataTable.wait(); - await apis.user.nodes.renameNode(folder1Id, folder1Renamed); - await page.refresh(); - await page.dataTable.wait(); - expect(await breadcrumb.currentItem.getText()).toEqual(folder1Renamed); - }); - - it('[C213240] Browser back navigates to previous location regardless of breadcrumb steps', async () => { - await page.clickPersonalFilesAndWait(); - await page.dataTable.doubleClickOnRowByName(parent); - await page.dataTable.doubleClickOnRowByName(subFolder1); - await page.dataTable.doubleClickOnRowByName(subFolder2); - await page.clickTrash(); - await page.dataTable.waitForEmptyState(); - await browser.navigate().back(); - const expectedBreadcrumb = ['Personal Files', parent, subFolder1, subFolder2]; - expect(await breadcrumb.getAllItems()).toEqual(expectedBreadcrumb); - }); - - describe('as admin', () => { - const user2 = `user2-${Utils.random()}`; - const userFolder = `userFolder-${Utils.random()}`; - let userFolderId: string; - const user2Api = new RepoClient(user2, user2); - - beforeAll(async () => { - await adminApiActions.createUser({ username: user2 }); - userFolderId = (await user2Api.nodes.createFolder(userFolder)).entry.id; - await loginPage.loginWithAdmin(); - await page.dataTable.waitForBody(); - - await page.dataTable.sortByModified('desc'); - }); - - afterAll(async () => { - await user2Api.nodes.deleteNodeById(userFolderId); - }); - - it(`[C260970] Breadcrumb on navigation to a user's home`, async () => { - await page.dataTable.doubleClickOnRowByName('User Homes'); - await page.dataTable.waitForBody(); - await page.dataTable.doubleClickOnRowByName(user2); - await page.dataTable.waitForBody(); - - expect(await breadcrumb.getAllItems()).toEqual(['Personal Files', 'User Homes', user2]); - await page.dataTable.doubleClickOnRowByName(userFolder); - expect(await breadcrumb.getAllItems()).toEqual(['Personal Files', 'User Homes', user2, userFolder]); - }); - }); -}); diff --git a/projects/aca-playwright-shared/src/api/index.ts b/projects/aca-playwright-shared/src/api/index.ts index 984a043ea..9514aaf4b 100644 --- a/projects/aca-playwright-shared/src/api/index.ts +++ b/projects/aca-playwright-shared/src/api/index.ts @@ -29,3 +29,5 @@ export * from './shared-links-api'; export * from './favorites-api'; export * from './user-actions'; export * from './people-api-models'; +export * from './nodes-api'; +export * from './sites-api'; diff --git a/projects/aca-playwright-shared/src/api/nodes-api.ts b/projects/aca-playwright-shared/src/api/nodes-api.ts new file mode 100755 index 000000000..ec0c0841b --- /dev/null +++ b/projects/aca-playwright-shared/src/api/nodes-api.ts @@ -0,0 +1,123 @@ +/*! + * Copyright © 2005-2023 Hyland Software, Inc. and its affiliates. All rights reserved. + * + * Alfresco Example Content Application + * + * This file is part of the Alfresco Example Content Application. + * If the software was purchased under a paid Alfresco license, the terms of + * the paid license agreement will prevail. Otherwise, the software is + * provided under the following open source license terms: + * + * The Alfresco Example Content Application is free software: you can redistribute it and/or modify + * it under the terms of the GNU Lesser General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * The Alfresco Example Content Application is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with Alfresco. If not, see . + */ + +import { ApiClientFactory } from './api-client-factory'; +import { NodeEntry } from '@alfresco/js-api'; +import { users } from '../base-config/global-variables'; +import { logger } from '@alfresco/adf-cli/scripts/logger'; + +export class NodesApi extends ApiClientFactory { + private apiService: ApiClientFactory; + + constructor() { + super(); + this.apiService = new ApiClientFactory(); + } + static async initialize(userProfile: keyof typeof users): Promise { + const classObj = new NodesApi(); + await classObj.apiService.setUpAcaBackend(userProfile); + return classObj; + } + + async createFolder( + name: string, + parentId: string = '-my-', + title: string = '', + description: string = '', + author: string = '', + aspectNames: string[] = null + ): Promise { + try { + return await this.createNode('cm:folder', name, parentId, title, description, null, author, null, aspectNames); + } catch (error) { + logger.error(`${this.constructor.name} ${this.createFolder.name}`, error); + return null; + } + } + + async createFile( + name: string, + parentId: string = '-my-', + title: string = '', + description: string = '', + author: string = '', + majorVersion: boolean = true, + aspectNames: string[] = null + ): Promise { + try { + return await this.createNode('cm:content', name, parentId, title, description, null, author, majorVersion, aspectNames); + } catch (error) { + logger.error(`${this.constructor.name} ${this.createFile.name}`, error); + return null; + } + } + + private async createNode( + nodeType: string, + name: string, + parentId: string = '-my-', + title: string = '', + description: string = '', + imageProps: any = null, + author: string = '', + majorVersion: boolean = true, + aspectNames: string[] = null + ): Promise { + if (!aspectNames) { + aspectNames = ['cm:versionable']; // workaround for REPO-4772 + } + const nodeBody = { + name, + nodeType, + relativePath: '/', + properties: { + 'cm:title': title, + 'cm:description': description, + 'cm:author': author + }, + aspectNames + }; + if (imageProps) { + nodeBody.properties = Object.assign(nodeBody.properties, imageProps); + } + + try { + return await this.apiService.nodes.createNode(parentId, nodeBody, { + majorVersion + }); + } catch (error) { + logger.error(`${this.constructor.name} ${this.createNode.name}`, error); + return null; + } + } + + async renameNode(nodeId: string, newName: string): Promise { + try { + return this.apiService.nodes.updateNode(nodeId, { name: newName }); + } catch (error) { + logger.error(`${this.constructor.name} ${this.renameNode.name}`, error); + return null; + } + } +} diff --git a/projects/aca-playwright-shared/src/api/sites-api.ts b/projects/aca-playwright-shared/src/api/sites-api.ts new file mode 100755 index 000000000..821516607 --- /dev/null +++ b/projects/aca-playwright-shared/src/api/sites-api.ts @@ -0,0 +1,68 @@ +/*! + * Copyright © 2005-2023 Hyland Software, Inc. and its affiliates. All rights reserved. + * + * Alfresco Example Content Application + * + * This file is part of the Alfresco Example Content Application. + * If the software was purchased under a paid Alfresco license, the terms of + * the paid license agreement will prevail. Otherwise, the software is + * provided under the following open source license terms: + * + * The Alfresco Example Content Application is free software: you can redistribute it and/or modify + * it under the terms of the GNU Lesser General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * The Alfresco Example Content Application is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with Alfresco. If not, see . + */ + +import { ApiClientFactory } from './api-client-factory'; +import { SiteBody, SiteEntry } from '@alfresco/js-api'; +import { users } from '../base-config/global-variables'; +import { logger } from '@alfresco/adf-cli/scripts/logger'; +import { SITE_VISIBILITY } from '../utils/configs'; + +export class SitesApi extends ApiClientFactory { + private apiService: ApiClientFactory; + + constructor() { + super(); + this.apiService = new ApiClientFactory(); + } + static async initialize(userProfile: keyof typeof users): Promise { + const classObj = new SitesApi(); + await classObj.apiService.setUpAcaBackend(userProfile); + return classObj; + } + + async createSite(title: string, visibility?: string, description?: string, siteId?: string): Promise { + const site = { + title, + visibility: visibility || SITE_VISIBILITY.PUBLIC, + description: description, + id: siteId || title + } as SiteBody; + + try { + return await this.apiService.sites.createSite(site); + } catch (error) { + logger.error(`SitesApi createSite : catch : `, error); + return null; + } + } + + async getDocLibId(siteId: string): Promise { + try { + return (await this.apiService.sites.listSiteContainers(siteId)).list.entries[0].entry.id; + } catch (error) { + logger.error(`SitesApi getDocLibId : catch : `, error); + return null; + } + } +} diff --git a/projects/aca-playwright-shared/src/fixtures/page-initialization.ts b/projects/aca-playwright-shared/src/fixtures/page-initialization.ts index 4615c8365..197e06347 100644 --- a/projects/aca-playwright-shared/src/fixtures/page-initialization.ts +++ b/projects/aca-playwright-shared/src/fixtures/page-initialization.ts @@ -36,7 +36,9 @@ import { FavoritesPageApi, TrashPage, UserActions, - LoginPage + LoginPage, + NodesApi, + SitesApi } from '../'; interface Pages { @@ -56,6 +58,8 @@ interface Api { shareAction: SharedLinksApi; favoritesPageAction: FavoritesPageApi; userActions: UserActions; + nodesApiAction: NodesApi; + sitesApiAction: SitesApi; } export const test = base.extend({ @@ -99,6 +103,14 @@ export const test = base.extend({ userActions: async ({}, use) => { await use(new UserActions()); }, + // eslint-disable-next-line no-empty-pattern + nodesApiAction: async ({}, use) => { + await use(await NodesApi.initialize('hruser')); + }, + // eslint-disable-next-line no-empty-pattern + sitesApiAction: async ({}, use) => { + await use(await SitesApi.initialize('hruser')); + }, myLibrariesPage: async ({ page }, use) => { await use(new MyLibrariesPage(page)); } diff --git a/projects/aca-playwright-shared/src/page-objects/components/breadcrumb/breadcrumb.component.ts b/projects/aca-playwright-shared/src/page-objects/components/breadcrumb/breadcrumb.component.ts new file mode 100755 index 000000000..970cf984b --- /dev/null +++ b/projects/aca-playwright-shared/src/page-objects/components/breadcrumb/breadcrumb.component.ts @@ -0,0 +1,51 @@ +/*! + * Copyright © 2005-2023 Hyland Software, Inc. and its affiliates. All rights reserved. + * + * Alfresco Example Content Application + * + * This file is part of the Alfresco Example Content Application. + * If the software was purchased under a paid Alfresco license, the terms of + * the paid license agreement will prevail. Otherwise, the software is + * provided under the following open source license terms: + * + * The Alfresco Example Content Application is free software: you can redistribute it and/or modify + * it under the terms of the GNU Lesser General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * The Alfresco Example Content Application is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with Alfresco. If not, see . + */ + +import { BaseComponent } from '.././base.component'; +import { Page } from '@playwright/test'; +export class Breadcrumb extends BaseComponent { + private static rootElement = 'adf-breadcrumb'; + public items = this.getChild('.adf-breadcrumb-item'); + public currentItem = this.getChild('.adf-breadcrumb-item-current'); + + constructor(page: Page) { + super(page, Breadcrumb.rootElement); + } + + async getAllItems(): Promise { + const itemElements = await this.page.$$('adf-breadcrumb .adf-breadcrumb-item'); + const itemTexts = await Promise.all( + itemElements.map(async (elem) => { + const text = await elem.innerText(); + return text.split('\nchevron_right')[0]; + }) + ); + return itemTexts; + } + + async clickItem(name: string): Promise { + const elem = this.getChild(`.adf-breadcrumb-item[title=${name}]`); + await elem.click(); + } +} diff --git a/projects/aca-playwright-shared/src/page-objects/components/index.ts b/projects/aca-playwright-shared/src/page-objects/components/index.ts index 8f3db9bde..5812c570d 100644 --- a/projects/aca-playwright-shared/src/page-objects/components/index.ts +++ b/projects/aca-playwright-shared/src/page-objects/components/index.ts @@ -35,3 +35,4 @@ export * from './adf-info-drawer.component'; export * from './viewer.component'; export * from './search/search-input.component'; export * from './search/search-overlay.components'; +export * from './breadcrumb/breadcrumb.component'; diff --git a/projects/aca-playwright-shared/src/page-objects/pages/my-libraries.page.ts b/projects/aca-playwright-shared/src/page-objects/pages/my-libraries.page.ts index b8b5191eb..747a8250e 100644 --- a/projects/aca-playwright-shared/src/page-objects/pages/my-libraries.page.ts +++ b/projects/aca-playwright-shared/src/page-objects/pages/my-libraries.page.ts @@ -32,7 +32,8 @@ import { MatMenuComponent, ViewerComponent, ViewerOverlayDialogComponent, - ContentNodeSelectorDialog + ContentNodeSelectorDialog, + Breadcrumb } from '../components'; export class MyLibrariesPage extends BasePage { @@ -49,6 +50,7 @@ export class MyLibrariesPage extends BasePage { public viewer = new ViewerComponent(this.page); public viewerDialog = new ViewerOverlayDialogComponent(this.page); public copyMoveDialog = new ContentNodeSelectorDialog(this.page); + public breadcrumb = new Breadcrumb(this.page); async selectCreateLibrary(): Promise { await this.acaHeader.createButton.click(); diff --git a/projects/aca-playwright-shared/src/page-objects/pages/personal-files.page.ts b/projects/aca-playwright-shared/src/page-objects/pages/personal-files.page.ts index bb63844c5..46e3bb798 100644 --- a/projects/aca-playwright-shared/src/page-objects/pages/personal-files.page.ts +++ b/projects/aca-playwright-shared/src/page-objects/pages/personal-files.page.ts @@ -24,7 +24,7 @@ import { Page } from '@playwright/test'; import { BasePage } from './base.page'; -import { DataTableComponent, MatMenuComponent, ViewerComponent } from '../components'; +import { Breadcrumb, DataTableComponent, MatMenuComponent, ViewerComponent } from '../components'; import { AcaHeader } from '../components/aca-header.component'; import { AdfFolderDialogComponent, PasswordOverlayDialogComponent, ViewerOverlayDialogComponent } from '../components/dialogs'; @@ -42,6 +42,7 @@ export class PersonalFilesPage extends BasePage { public viewer = new ViewerComponent(this.page); public passwordDialog = new PasswordOverlayDialogComponent(this.page); public viewerDialog = new ViewerOverlayDialogComponent(this.page); + public breadcrumb = new Breadcrumb(this.page); async selectCreateFolder(): Promise { await this.acaHeader.createButton.click(); diff --git a/projects/aca-playwright-shared/src/utils/configs.ts b/projects/aca-playwright-shared/src/utils/configs.ts new file mode 100755 index 000000000..051541f4f --- /dev/null +++ b/projects/aca-playwright-shared/src/utils/configs.ts @@ -0,0 +1,29 @@ +/*! + * Copyright © 2005-2023 Hyland Software, Inc. and its affiliates. All rights reserved. + * + * Alfresco Example Content Application + * + * This file is part of the Alfresco Example Content Application. + * If the software was purchased under a paid Alfresco license, the terms of + * the paid license agreement will prevail. Otherwise, the software is + * provided under the following open source license terms: + * + * The Alfresco Example Content Application is free software: you can redistribute it and/or modify + * it under the terms of the GNU Lesser General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * The Alfresco Example Content Application is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with Alfresco. If not, see . + */ + +export const SITE_VISIBILITY = { + PUBLIC: 'PUBLIC', + MODERATED: 'MODERATED', + PRIVATE: 'PRIVATE' +}; diff --git a/projects/aca-playwright-shared/src/utils/index.ts b/projects/aca-playwright-shared/src/utils/index.ts index d6ac3086e..ae27321d2 100644 --- a/projects/aca-playwright-shared/src/utils/index.ts +++ b/projects/aca-playwright-shared/src/utils/index.ts @@ -29,3 +29,4 @@ export * from './state-helper'; export * from './folder-errors'; export * from './utils'; export * from './library-errors'; +export * from './configs';