diff --git a/.eslintrc.json b/.eslintrc.json index b8bc07be7b..1941c44e28 100644 --- a/.eslintrc.json +++ b/.eslintrc.json @@ -329,7 +329,7 @@ } }, { - "files": ["*.e2e.ts", "*.ts"], + "files": ["*.e2e.ts"], "extends": ["plugin:playwright/playwright-test"], "rules": { "no-empty-pattern": "off", diff --git a/e2e/playwright/delete-actions/.eslintrc.json b/e2e/playwright/delete-actions/.eslintrc.json new file mode 100644 index 0000000000..1777b0a6ee --- /dev/null +++ b/e2e/playwright/delete-actions/.eslintrc.json @@ -0,0 +1,26 @@ +{ + "extends": "../../../.eslintrc.json", + "ignorePatterns": [ + "!**/*" + ], + "overrides": [ + { + "files": [ + "*.ts" + ], + "parserOptions": { + "project": [ + "e2e/playwright/delete-actions/tsconfig.e2e.json" + ], + "createDefaultProgram": true + }, + "plugins": [ + "rxjs", + "unicorn" + ], + "rules": { + "@typescript-eslint/no-floating-promises": "off" + } + } + ] +} diff --git a/e2e/playwright/delete-actions/exclude.tests.json b/e2e/playwright/delete-actions/exclude.tests.json new file mode 100644 index 0000000000..0967ef424b --- /dev/null +++ b/e2e/playwright/delete-actions/exclude.tests.json @@ -0,0 +1 @@ +{} diff --git a/e2e/playwright/delete-actions/playwright.config.ts b/e2e/playwright/delete-actions/playwright.config.ts new file mode 100644 index 0000000000..b0779e871f --- /dev/null +++ b/e2e/playwright/delete-actions/playwright.config.ts @@ -0,0 +1,42 @@ +/*! + * Copyright © 2005-2024 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, 'Delete Actions'), + projects: [ + { + name: 'Delete Actions', + testDir: './src/tests', + use: {} + } + ] +}; + +export default config; diff --git a/e2e/playwright/delete-actions/project.json b/e2e/playwright/delete-actions/project.json new file mode 100644 index 0000000000..0e03b9f29e --- /dev/null +++ b/e2e/playwright/delete-actions/project.json @@ -0,0 +1,22 @@ +{ + "name": "delete-actions-e2e", + "$schema": "../../../node_modules/nx/schemas/project-schema.json", + "sourceRoot": "e2e/playwright/delete-actions", + "projectType": "application", + "targets": { + "e2e": { + "executor": "nx:run-commands", + "options": { + "commands": ["npx playwright test --config=e2e/playwright/delete-actions/playwright.config.ts"] + }, + "configurations": { + "production": { + "devServerTarget": "content-ce:serve:production" + } + } + }, + "lint": { + "executor": "@angular-eslint/builder:lint" + } + } +} diff --git a/e2e/playwright/delete-actions/src/tests/delete-undo-delete.e2e.ts b/e2e/playwright/delete-actions/src/tests/delete-undo-delete.e2e.ts new file mode 100755 index 0000000000..40a1e12252 --- /dev/null +++ b/e2e/playwright/delete-actions/src/tests/delete-undo-delete.e2e.ts @@ -0,0 +1,238 @@ +/*! + * Copyright © 2005-2024 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, NodesApi, Utils, test, TrashcanApi } from '@alfresco/playwright-shared'; + +test.describe('Delete and undo delete', () => { + const apiClientFactory = new ApiClientFactory(); + let nodesApi: NodesApi; + let trashcanApi: TrashcanApi; + + const username = `user-${Utils.random()}`; + + test.beforeAll(async () => { + try { + await apiClientFactory.setUpAcaBackend('admin'); + await apiClientFactory.createUser({ username }); + nodesApi = await NodesApi.initialize(username, username); + trashcanApi = await TrashcanApi.initialize(username, username); + } catch (error) { + console.error(`beforeAll failed : ${error}`); + } + }); + + test.afterAll(async () => { + await Utils.deleteNodesSitesEmptyTrashcan(nodesApi, trashcanApi, 'afterAll failed'); + }); + + test.describe('on Personal Files', () => { + const file1 = `file1-${Utils.random()}.txt`; + const file2 = `file2-${Utils.random()}.txt`; + const file3 = `file3-${Utils.random()}.txt`; + const file4 = `file4-${Utils.random()}.txt`; + const file5 = `file5-${Utils.random()}.txt`; + const file6 = `file6-${Utils.random()}.txt`; + const file7 = `file7-${Utils.random()}.txt`; + + const folder1 = `folder1-${Utils.random()}`; + const folder2 = `folder2-${Utils.random()}`; + const folder3 = `folder3-${Utils.random()}`; + const folder4 = `folder4-${Utils.random()}`; + const folder5 = `folder5-${Utils.random()}`; + const folder6 = `folder6-${Utils.random()}`; + + const file1InFolder = `file1InFolder-${Utils.random()}.txt`; + const file2InFolder = `file2InFolder-${Utils.random()}.txt`; + const fileLocked1 = `fileLocked1-${Utils.random()}.txt`; + let fileLocked1Id: string; + const fileLocked2 = `fileLocked2-${Utils.random()}.txt`; + let fileLocked2Id: string; + const fileLocked3 = `fileLocked3-${Utils.random()}.txt`; + let fileLocked3Id: string; + const fileLocked4 = `fileLocked4-${Utils.random()}.txt`; + let fileLocked4Id: string; + + const parent = `parentPF-${Utils.random()}`; + let parentId: string; + + test.beforeAll(async () => { + parentId = (await nodesApi.createFolder(parent)).entry.id; + + await nodesApi.createFile(file1, parentId); + await nodesApi.createFile(file2, parentId); + await nodesApi.createFile(file3, parentId); + await nodesApi.createFile(file4, parentId); + await nodesApi.createFile(file5, parentId); + await nodesApi.createFile(file6, parentId); + await nodesApi.createFile(file7, parentId); + + const folder1Id = (await nodesApi.createFolder(folder1, parentId)).entry.id; + const folder2Id = (await nodesApi.createFolder(folder2, parentId)).entry.id; + const folder3Id = (await nodesApi.createFolder(folder3, parentId)).entry.id; + const folder4Id = (await nodesApi.createFolder(folder4, parentId)).entry.id; + const folder5Id = (await nodesApi.createFolder(folder5, parentId)).entry.id; + const folder6Id = (await nodesApi.createFolder(folder6, parentId)).entry.id; + + await nodesApi.createFile(file1InFolder, folder1Id); + fileLocked1Id = (await nodesApi.createFile(fileLocked1, folder2Id)).entry.id; + fileLocked2Id = (await nodesApi.createFile(fileLocked2, folder3Id)).entry.id; + fileLocked3Id = (await nodesApi.createFile(fileLocked3, folder4Id)).entry.id; + fileLocked4Id = (await nodesApi.createFile(fileLocked4, folder5Id)).entry.id; + await nodesApi.createFile(file2InFolder, folder6Id); + + await nodesApi.lockNodes([fileLocked1Id, fileLocked2Id, fileLocked3Id, fileLocked4Id], 'FULL'); + }); + + test.beforeEach(async ({ loginPage, personalFiles }) => { + await Utils.tryLoginUser(loginPage, username, username, 'beforeEach failed'); + await personalFiles.navigate(); + await personalFiles.dataTable.performClickFolderOrFileToOpen(parent); + }); + + test.afterAll(async () => { + await nodesApi.unlockNodes([fileLocked1Id, fileLocked2Id, fileLocked3Id, fileLocked4Id]); + await Utils.deleteNodesSitesEmptyTrashcan(nodesApi, trashcanApi, 'afterAll failed'); + }); + + test('[C217125] delete a file and check notification', async ({ personalFiles, trashPage }) => { + let items = await personalFiles.dataTable.getRowsCount(); + await personalFiles.dataTable.selectItem(file1); + await personalFiles.acaHeader.clickMoreActions(); + await personalFiles.matMenu.clickMenuItem('Delete'); + const message = await personalFiles.snackBar.getSnackBarMessage(); + expect(message).toContain(`${file1} deleted`); + const action = await personalFiles.snackBar.getSnackBarActionText(); + expect(action).toContain('Undo'); + await personalFiles.snackBar.closeIcon.click(); + expect(await personalFiles.dataTable.isItemPresent(file1)).toBeFalsy(); + items--; + expect(await personalFiles.pagination.getMaxRange()).toEqual(` Showing 1-${items} of ${items} `); + await trashPage.navigate(); + expect(await personalFiles.dataTable.isItemPresent(file1)).toBeTruthy(); + }); + + test('[C280502] delete multiple files and check notification', async ({ personalFiles, trashPage }) => { + let items = await personalFiles.dataTable.getRowsCount(); + await personalFiles.dataTable.selectItem(file2); + await personalFiles.page.waitForTimeout(1500); + await personalFiles.dataTable.selectItem(file3); + await personalFiles.acaHeader.clickMoreActions(); + await personalFiles.matMenu.clickMenuItem('Delete'); + await personalFiles.snackBar.verifySnackBarActionText(`Deleted 2 items`); + await personalFiles.snackBar.closeIcon.click(); + expect(await personalFiles.dataTable.isItemPresent(file2)).toBeFalsy(); + expect(await personalFiles.dataTable.isItemPresent(file3)).toBeFalsy(); + items = items - 2; + expect(await personalFiles.pagination.getMaxRange()).toEqual(` Showing 1-${items} of ${items} `); + await trashPage.navigate(); + expect(await personalFiles.dataTable.isItemPresent(file2)).toBeTruthy(); + expect(await personalFiles.dataTable.isItemPresent(file3)).toBeTruthy(); + }); + + test('[C217126] delete a folder with content', async ({ personalFiles, trashPage }) => { + let items = await personalFiles.dataTable.getRowsCount(); + await personalFiles.dataTable.selectItem(folder1); + await personalFiles.acaHeader.clickMoreActions(); + await personalFiles.matMenu.clickMenuItem('Delete'); + await personalFiles.snackBar.closeIcon.click(); + expect(await personalFiles.dataTable.isItemPresent(folder1)).toBeFalsy(); + items--; + expect(await personalFiles.pagination.getMaxRange()).toEqual(` Showing 1-${items} of ${items} `); + await trashPage.navigate(); + expect(await personalFiles.dataTable.isItemPresent(folder1)).toBeTruthy(); + expect(await personalFiles.dataTable.isItemPresent(file1InFolder)).toBeFalsy(); + }); + + test('[C217127] delete a folder containing locked files', async ({ personalFiles, trashPage }) => { + await personalFiles.dataTable.selectItem(folder2); + await personalFiles.acaHeader.clickMoreActions(); + await personalFiles.matMenu.clickMenuItem('Delete'); + await personalFiles.snackBar.verifySnackBarActionText(`${folder2} couldn't be deleted`); + await expect(personalFiles.snackBar.actionButton).toBeHidden(); + expect(await personalFiles.dataTable.isItemPresent(folder2)).toBeTruthy(); + await trashPage.navigate(); + expect(await personalFiles.dataTable.isItemPresent(folder2)).toBeFalsy(); + expect(await personalFiles.dataTable.isItemPresent(fileLocked1)).toBeFalsy(); + }); + + test('[C217129] notification on multiple items deletion - some items fail to delete', async ({ personalFiles }) => { + await personalFiles.dataTable.selectItem(file4); + await personalFiles.page.waitForTimeout(1500); + await personalFiles.dataTable.selectItem(folder3); + await personalFiles.acaHeader.clickMoreActions(); + await personalFiles.matMenu.clickMenuItem('Delete'); + await personalFiles.snackBar.verifySnackBarActionText(`Deleted 1 item, 1 couldn't be deleted`); + const action = await personalFiles.snackBar.getSnackBarActionText(); + expect(action).toContain('Undo'); + }); + + test('[C217130] notification on multiple items deletion - all items fail to delete', async ({ personalFiles }) => { + await personalFiles.dataTable.selectItem(folder4); + await personalFiles.page.waitForTimeout(1500); + await personalFiles.dataTable.selectItem(folder5); + await personalFiles.acaHeader.clickMoreActions(); + await personalFiles.matMenu.clickMenuItem('Delete'); + await personalFiles.snackBar.verifySnackBarActionText(`2 items couldn't be deleted`); + await expect(personalFiles.snackBar.actionButton).toBeHidden(); + }); + + test('[C217132] undo delete of file', async ({ personalFiles }) => { + const items = await personalFiles.dataTable.getRowsCount(); + + await personalFiles.dataTable.selectItem(file5); + await personalFiles.acaHeader.clickMoreActions(); + await personalFiles.matMenu.clickMenuItem('Delete'); + + await personalFiles.snackBar.clickSnackBarAction(); + await personalFiles.dataTable.spinnerWaitForReload(); + expect(await personalFiles.dataTable.isItemPresent(file5)).toBeTruthy(); + expect(await personalFiles.pagination.getRange()).toContain(`1-${items} of ${items}`); + }); + + test('[C280503] undo delete of folder with content', async ({ personalFiles }) => { + await personalFiles.dataTable.selectItem(folder6); + await personalFiles.acaHeader.clickMoreActions(); + await personalFiles.matMenu.clickMenuItem('Delete'); + await personalFiles.snackBar.clickSnackBarAction(); + await personalFiles.dataTable.spinnerWaitForReload(); + expect(await personalFiles.dataTable.isItemPresent(folder6)).toBeTruthy(); + await personalFiles.dataTable.performClickFolderOrFileToOpen(folder6); + await personalFiles.dataTable.spinnerWaitForReload(); + expect(await personalFiles.dataTable.isItemPresent(file2InFolder)).toBeTruthy(); + }); + + test('[C280504] undo delete of multiple files', async ({ personalFiles }) => { + await personalFiles.dataTable.selectItem(file6); + await personalFiles.page.waitForTimeout(1500); + await personalFiles.dataTable.selectItem(file7); + await personalFiles.acaHeader.clickMoreActions(); + await personalFiles.matMenu.clickMenuItem('Delete'); + await personalFiles.snackBar.clickSnackBarAction(); + await personalFiles.dataTable.spinnerWaitForReload(); + expect(await personalFiles.dataTable.isItemPresent(file6)).toBeTruthy(); + expect(await personalFiles.dataTable.isItemPresent(file7)).toBeTruthy(); + }); + }); +}); diff --git a/e2e/playwright/delete-actions/src/tests/permanently-delete.e2e.ts b/e2e/playwright/delete-actions/src/tests/permanently-delete.e2e.ts new file mode 100755 index 0000000000..a8de1fdc58 --- /dev/null +++ b/e2e/playwright/delete-actions/src/tests/permanently-delete.e2e.ts @@ -0,0 +1,143 @@ +/*! + * Copyright © 2005-2024 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, NodesApi, Utils, test, TrashcanApi, SitesApi } from '@alfresco/playwright-shared'; + +test.describe('Delete and undo delete', () => { + const apiClientFactory = new ApiClientFactory(); + let nodesApi: NodesApi; + let trashcanApi: TrashcanApi; + let sitesApi: SitesApi; + + const username = `user-${Utils.random()}`; + + test.beforeAll(async () => { + try { + await apiClientFactory.setUpAcaBackend('admin'); + await apiClientFactory.createUser({ username }); + trashcanApi = await TrashcanApi.initialize(username, username); + nodesApi = await NodesApi.initialize(username, username); + + sitesApi = await SitesApi.initialize(username, username); + } catch (error) { + console.error(`beforeAll failed : ${error}`); + } + }); + + test.afterAll(async () => { + await Utils.deleteNodesSitesEmptyTrashcan(nodesApi, trashcanApi, 'afterAll failed'); + }); + + test.describe('Permanently delete from Trash', () => { + const file1 = `file1-${Utils.random()}.txt`; + const file2 = `file2-${Utils.random()}.txt`; + const file3 = `file3-${Utils.random()}.txt`; + let filesIds; + + const folder1 = `folder1-${Utils.random()}`; + const folder2 = `folder2-${Utils.random()}`; + let foldersIds; + + const site = `site-${Utils.random()}`; + + test.beforeAll(async () => { + filesIds = (await nodesApi.createFiles([file1, file2, file3])).list.entries.map((entries) => entries.entry.id); + foldersIds = (await nodesApi.createFolders([folder1, folder2])).list.entries.map((entries) => entries.entry.id); + await sitesApi.createSite(site); + await nodesApi.deleteNodes([...filesIds, ...foldersIds], false); + await sitesApi.deleteSites([site], false); + }); + + test.beforeEach(async ({ loginPage, trashPage }) => { + await Utils.tryLoginUser(loginPage, username, username, 'beforeEach failed'); + await trashPage.navigate(); + }); + + test.afterAll(async () => { + await Utils.deleteNodesSitesEmptyTrashcan(nodesApi, trashcanApi, 'afterAll failed'); + }); + + test('[C217091] delete a file', async ({ trashPage }) => { + await trashPage.dataTable.selectItem(file1); + await trashPage.acaHeader.permanentlyDeleteButton.click(); + await trashPage.deleteDialog.deleteButton.click(); + + await trashPage.snackBar.verifySnackBarActionText(`${file1} deleted`); + expect(await trashPage.dataTable.isItemPresent(file1)).toBeFalsy(); + }); + + test('[C280416] delete a folder', async ({ trashPage }) => { + await trashPage.dataTable.selectItem(folder1); + await trashPage.acaHeader.permanentlyDeleteButton.click(); + await trashPage.deleteDialog.deleteButton.click(); + + await trashPage.snackBar.verifySnackBarActionText(`${folder1} deleted`); + expect(await trashPage.dataTable.isItemPresent(folder1)).toBeFalsy(); + }); + + test('[C290103] delete a library', async ({ trashPage }) => { + await trashPage.dataTable.selectItem(site); + await trashPage.acaHeader.permanentlyDeleteButton.click(); + await trashPage.deleteDialog.deleteButton.click(); + + await trashPage.snackBar.verifySnackBarActionText(`${site} deleted`); + expect(await trashPage.dataTable.isItemPresent(site)).toBeFalsy(); + }); + + test('[C280417] delete multiple items', async ({ trashPage }) => { + await trashPage.dataTable.selectItem(file2); + await trashPage.page.waitForTimeout(1500); + await trashPage.dataTable.selectItem(folder2); + await trashPage.acaHeader.permanentlyDeleteButton.click(); + await trashPage.deleteDialog.deleteButton.click(); + + await trashPage.snackBar.verifySnackBarActionText(`2 items deleted`); + expect(await trashPage.dataTable.isItemPresent(file2)).toBeFalsy(); + expect(await trashPage.dataTable.isItemPresent(folder2)).toBeFalsy(); + }); + + test('[C269113] Confirmation dialog UI', async ({ trashPage }) => { + await trashPage.dataTable.selectItem(file3); + await trashPage.acaHeader.permanentlyDeleteButton.click(); + await trashPage.deleteDialog.waitForDialog(); + + expect(await trashPage.deleteDialog.isDialogOpen()).toBeTruthy(); + expect(await trashPage.deleteDialog.getDialogTitle()).toContain('Delete from trash'); + expect(await trashPage.deleteDialog.getDialogDescription()).toContain('This will permanently remove the selected item(s)'); + expect(await trashPage.deleteDialog.isDeleteEnabled()).toBeTruthy(); + expect(await trashPage.deleteDialog.isKeepEnabled()).toBeTruthy(); + }); + + test('[C269115] Keep action cancels the deletion', async ({ trashPage }) => { + await trashPage.dataTable.selectItem(file3); + await trashPage.acaHeader.permanentlyDeleteButton.click(); + await trashPage.deleteDialog.waitForDialog(); + + expect(await trashPage.deleteDialog.isKeepEnabled()).toBeTruthy(); + await trashPage.deleteDialog.keepButton.click(); + expect(await trashPage.dataTable.isItemPresent(file3)).toBeTruthy(); + }); + }); +}); diff --git a/e2e/playwright/delete-actions/src/tests/restore.e2e.ts b/e2e/playwright/delete-actions/src/tests/restore.e2e.ts new file mode 100755 index 0000000000..87f7a1ca40 --- /dev/null +++ b/e2e/playwright/delete-actions/src/tests/restore.e2e.ts @@ -0,0 +1,245 @@ +/*! + * Copyright © 2005-2024 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, NodesApi, Utils, test, TrashcanApi, SitesApi, APP_ROUTES, TrashPage } from '@alfresco/playwright-shared'; + +test.describe('Restore from Trash', () => { + const apiClientFactory = new ApiClientFactory(); + let nodesApi: NodesApi; + let trashcanApi: TrashcanApi; + let sitesApi: SitesApi; + const username = `user-${Utils.random()}`; + + test.beforeAll(async () => { + try { + await apiClientFactory.setUpAcaBackend('admin'); + await apiClientFactory.createUser({ username }); + sitesApi = await SitesApi.initialize(username, username); + + nodesApi = await NodesApi.initialize(username, username); + trashcanApi = await TrashcanApi.initialize(username, username); + } catch (error) { + console.error(`beforeAll failed : ${error}`); + } + }); + + test.afterAll(async () => { + await Utils.deleteNodesSitesEmptyTrashcan(nodesApi, trashcanApi, 'afterAll failed'); + }); + + test.describe('successful restore', () => { + let fileId1: string; + const file1 = `file1-${Utils.random()}.txt`; + let folderId1: string; + const folder1 = `folder1-${Utils.random()}`; + let fileId2: string; + const file2 = `file2-${Utils.random()}.txt`; + let folderId2: string; + const folder2 = `folder2-${Utils.random()}`; + let fileId3: string; + const file3 = `file3-${Utils.random()}.txt`; + const site1 = `site1-${Utils.random()}`; + + test.beforeAll(async () => { + fileId1 = (await nodesApi.createFile(file1)).entry.id; + folderId1 = (await nodesApi.createFolder(folder1)).entry.id; + fileId2 = (await nodesApi.createFile(file2)).entry.id; + folderId2 = (await nodesApi.createFolder(folder2)).entry.id; + fileId3 = (await nodesApi.createFile(file3)).entry.id; + await sitesApi.createSite(site1); + await nodesApi.deleteNodes([fileId1, folderId1, fileId2, folderId2, fileId3], false); + await sitesApi.deleteSites([site1], false); + }); + + test.beforeEach(async ({ trashPage, loginPage }) => { + await Utils.tryLoginUser(loginPage, username, username, 'beforeEach failed'); + await trashPage.navigate(); + }); + + test.afterAll(async () => { + await Utils.deleteNodesSitesEmptyTrashcan(nodesApi, trashcanApi, 'afterAll failed'); + }); + + async function restoreNode(trashPage: TrashPage, nodeName: string) { + await trashPage.dataTable.selectItem(nodeName); + await trashPage.acaHeader.restoreButton.click(); + await trashPage.snackBar.verifySnackBarActionText(`${nodeName} restored`); + const action = await trashPage.snackBar.getSnackBarActionText(); + expect(action).toContain('View'); + expect(await trashPage.dataTable.isItemPresent(nodeName)).toBeFalsy(); + } + + test('[C217177] restore file', async ({ trashPage, personalFiles }) => { + await restoreNode(trashPage, file1); + await personalFiles.navigate(); + expect(await personalFiles.dataTable.isItemPresent(file1)).toBeTruthy(); + }); + + test('[C280438] restore folder', async ({ trashPage, personalFiles }) => { + await restoreNode(trashPage, folder1); + await personalFiles.navigate(); + expect(await personalFiles.dataTable.isItemPresent(folder1)).toBeTruthy(); + }); + + test('[C290104] restore library', async ({ trashPage, myLibrariesPage }) => { + await restoreNode(trashPage, site1); + await myLibrariesPage.navigate(); + expect(await myLibrariesPage.dataTable.isItemPresent(site1)).toBeTruthy(); + }); + + test('[C217182] restore multiple items', async ({ trashPage, personalFiles }) => { + await trashPage.dataTable.selectItem(file2); + await trashPage.page.waitForTimeout(1500); + await trashPage.dataTable.selectItem(folder2); + await trashPage.acaHeader.restoreButton.click(); + await trashPage.snackBar.verifySnackBarActionText(`Restore successful`); + const action = await trashPage.snackBar.getSnackBarActionText(); + expect(action).not.toContain('View'); + expect(await trashPage.dataTable.isItemPresent(file2)).toBeFalsy(); + expect(await trashPage.dataTable.isItemPresent(folder2)).toBeFalsy(); + await personalFiles.navigate(); + expect(await personalFiles.dataTable.isItemPresent(file2)).toBeTruthy(); + expect(await personalFiles.dataTable.isItemPresent(folder2)).toBeTruthy(); + }); + + test('[C217181] View from notification', async ({ trashPage, personalFiles }) => { + await trashPage.dataTable.selectItem(file3); + await trashPage.acaHeader.restoreButton.click(); + await trashPage.snackBar.clickSnackBarAction(); + await trashPage.dataTable.spinnerWaitForReload(); + expect(await trashPage.sidenav.isActive('Personal Files')).toBeTruthy(); + expect(personalFiles.page.url()).toContain(APP_ROUTES.PERSONAL_FILES); + }); + }); + + test.describe('failure to restore', () => { + const randomText = Utils.random(); + let file1Id1: string; + const file1 = `file1-${randomText}.txt`; + const file1copy = `file1-${randomText}.txt`; + let file2Id: string; + const file2 = `file2-${randomText}.txt`; + let folder1Id: string; + const folder1 = `folder1-${randomText}`; + let folder2Id: string; + const folder2 = `folder2-${randomText}`; + + test.beforeAll(async () => { + folder1Id = (await nodesApi.createFolder(folder1)).entry.id; + folder2Id = (await nodesApi.createFolder(folder2)).entry.id; + file1Id1 = (await nodesApi.createFile(file1, folder1Id)).entry.id; + file2Id = (await nodesApi.createFile(file2, folder2Id)).entry.id; + await nodesApi.deleteNodes([file2Id, folder2Id, file1Id1], false); + await nodesApi.createFile(file1copy, folder1Id); + }); + + test.beforeEach(async ({ loginPage, trashPage }) => { + await Utils.tryLoginUser(loginPage, username, username, 'beforeEach failed'); + await trashPage.navigate(); + }); + + test.afterAll(async () => { + await Utils.deleteNodesSitesEmptyTrashcan(nodesApi, trashcanApi, 'afterAll failed'); + }); + + test('[C217178] Restore a file when another file with same name exists on the restore location', async ({ trashPage }) => { + await trashPage.dataTable.selectItem(file1); + await trashPage.acaHeader.restoreButton.click(); + await trashPage.snackBar.verifySnackBarActionText(`Can't restore, ${file1} already exists`); + }); + + test('[C217179] Restore a file when original location no longer exists', async ({ trashPage }) => { + await trashPage.dataTable.selectItem(file2); + await trashPage.acaHeader.restoreButton.click(); + await trashPage.snackBar.verifySnackBarActionText(`Can't restore ${file2}, the original location no longer exists`); + }); + }); + + test.describe('Notification on partial success', () => { + const folder1 = `folder1-${Utils.random()}.txt`; + let folder1Id: string; + const folder2 = `folder2-${Utils.random()}.txt`; + let folder2Id: string; + const file1 = `file-${Utils.random()}.txt`; + let file1Id: string; + const file2 = `file-${Utils.random()}.txt`; + let file2Id: string; + + const folder3 = `folder3-${Utils.random()}.txt`; + let folder3Id: string; + const folder4 = `folder4-${Utils.random()}.txt`; + let folder4Id: string; + const file3 = `file3-${Utils.random()}.txt`; + let file3Id: string; + const file4 = `file4-${Utils.random()}.txt`; + let file4Id: string; + const file5 = `file5-${Utils.random()}.txt`; + let file5Id: string; + + test.beforeAll(async () => { + try { + folder1Id = (await nodesApi.createFolder(folder1)).entry.id; + file1Id = (await nodesApi.createFile(file1, folder1Id)).entry.id; + folder2Id = (await nodesApi.createFolder(folder2)).entry.id; + file2Id = (await nodesApi.createFile(file2, folder2Id)).entry.id; + await nodesApi.deleteNodes([file1Id, folder1Id, file2Id], false); + + folder3Id = (await nodesApi.createFolder(folder3)).entry.id; + file3Id = (await nodesApi.createFile(file3, folder3Id)).entry.id; + file4Id = (await nodesApi.createFile(file4, folder3Id)).entry.id; + folder4Id = (await nodesApi.createFolder(folder4)).entry.id; + file5Id = (await nodesApi.createFile(file5, folder4Id)).entry.id; + await nodesApi.deleteNodes([file3Id, file4Id, folder3Id, file5Id], false); + } catch {} + }); + + test.beforeEach(async ({ loginPage, trashPage }) => { + await Utils.tryLoginUser(loginPage, username, username, 'beforeEach failed'); + await trashPage.navigate(); + }); + + test.afterAll(async () => { + await Utils.deleteNodesSitesEmptyTrashcan(nodesApi, trashcanApi, 'afterAll failed'); + }); + + test('[C217183] one failure', async ({ trashPage }) => { + await trashPage.dataTable.selectItem(file1); + await trashPage.page.waitForTimeout(1500); + await trashPage.dataTable.selectItem(file2); + await trashPage.acaHeader.restoreButton.click(); + await trashPage.snackBar.verifySnackBarActionText(`Can't restore ${file1}, the original location no longer exists`); + }); + + test('[C217184] multiple failures', async ({ trashPage }) => { + await trashPage.dataTable.selectItem(file3); + await trashPage.page.waitForTimeout(1500); + await trashPage.dataTable.selectItem(file4); + await trashPage.page.waitForTimeout(1500); + await trashPage.dataTable.selectItem(file5); + await trashPage.acaHeader.restoreButton.click(); + await trashPage.snackBar.verifySnackBarActionText('2 items not restored because of issues with the restore location'); + }); + }); +}); diff --git a/e2e/playwright/delete-actions/tsconfig.e2e.adf.json b/e2e/playwright/delete-actions/tsconfig.e2e.adf.json new file mode 100644 index 0000000000..87cbcf775a --- /dev/null +++ b/e2e/playwright/delete-actions/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/delete-actions/tsconfig.e2e.json b/e2e/playwright/delete-actions/tsconfig.e2e.json new file mode 100755 index 0000000000..c317985239 --- /dev/null +++ b/e2e/playwright/delete-actions/tsconfig.e2e.json @@ -0,0 +1,15 @@ +{ + "extends": "../../../tsconfig.json", + "compilerOptions": { + "outDir": "../../out-tsc/e2e", + "baseUrl": "./", + "module": "commonjs", + "target": "es2017", + "types": ["jasmine", "jasminewd2", "node", "@playwright/test"], + "skipLibCheck": true, + "paths": { + "@alfresco/playwright-shared": ["../../../projects/aca-playwright-shared/src/index.ts"] + } + }, + "exclude": ["node_modules"] +} diff --git a/e2e/protractor/suites/actions/delete/delete-undo-delete.test.ts b/e2e/protractor/suites/actions/delete/delete-undo-delete.test.ts deleted file mode 100755 index d43bff3f86..0000000000 --- a/e2e/protractor/suites/actions/delete/delete-undo-delete.test.ts +++ /dev/null @@ -1,230 +0,0 @@ -/*! - * Copyright © 2005-2024 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 { AdminActions, UserActions, LoginPage, BrowsingPage, RepoClient, Utils } from '@alfresco/aca-testing-shared'; - -describe('Delete and undo delete', () => { - const username = `user-${Utils.random()}`; - - const apis = { - user: new RepoClient(username, username) - }; - - const loginPage = new LoginPage(); - const page = new BrowsingPage(); - const { dataTable, toolbar } = page; - - const adminApiActions = new AdminActions(); - const userActions = new UserActions(); - - beforeAll(async () => { - await adminApiActions.createUser({ username }); - await userActions.login(username, username); - }); - - afterAll(async () => { - await userActions.login(username, username); - await userActions.emptyTrashcan(); - }); - - describe('on Personal Files', () => { - const file1 = `file1-${Utils.random()}.txt`; - const file2 = `file2-${Utils.random()}.txt`; - const file3 = `file3-${Utils.random()}.txt`; - const file4 = `file4-${Utils.random()}.txt`; - const file5 = `file5-${Utils.random()}.txt`; - const file6 = `file6-${Utils.random()}.txt`; - const file7 = `file7-${Utils.random()}.txt`; - - const folder1 = `folder1-${Utils.random()}`; - const folder2 = `folder2-${Utils.random()}`; - const folder3 = `folder3-${Utils.random()}`; - const folder4 = `folder4-${Utils.random()}`; - const folder5 = `folder5-${Utils.random()}`; - const folder6 = `folder6-${Utils.random()}`; - - const file1InFolder = `file1InFolder-${Utils.random()}.txt`; - const file2InFolder = `file2InFolder-${Utils.random()}.txt`; - const fileLocked1 = `fileLocked1-${Utils.random()}.txt`; - let fileLocked1Id: string; - const fileLocked2 = `fileLocked2-${Utils.random()}.txt`; - let fileLocked2Id: string; - const fileLocked3 = `fileLocked3-${Utils.random()}.txt`; - let fileLocked3Id: string; - const fileLocked4 = `fileLocked4-${Utils.random()}.txt`; - let fileLocked4Id: string; - - const parent = `parentPF-${Utils.random()}`; - let parentId: string; - - beforeAll(async () => { - parentId = await apis.user.createFolder(parent); - - await apis.user.createFile(file1, parentId); - await apis.user.createFile(file2, parentId); - await apis.user.createFile(file3, parentId); - await apis.user.createFile(file4, parentId); - await apis.user.createFile(file5, parentId); - await apis.user.createFile(file6, parentId); - await apis.user.createFile(file7, parentId); - - const folder1Id = await apis.user.createFolder(folder1, parentId); - const folder2Id = await apis.user.createFolder(folder2, parentId); - const folder3Id = await apis.user.createFolder(folder3, parentId); - const folder4Id = await apis.user.createFolder(folder4, parentId); - const folder5Id = await apis.user.createFolder(folder5, parentId); - const folder6Id = await apis.user.createFolder(folder6, parentId); - - await apis.user.createFile(file1InFolder, folder1Id); - fileLocked1Id = await apis.user.createFile(fileLocked1, folder2Id); - fileLocked2Id = await apis.user.createFile(fileLocked2, folder3Id); - fileLocked3Id = await apis.user.createFile(fileLocked3, folder4Id); - fileLocked4Id = await apis.user.createFile(fileLocked4, folder5Id); - await apis.user.createFile(file2InFolder, folder6Id); - - await userActions.lockNodes([fileLocked1Id, fileLocked2Id, fileLocked3Id, fileLocked4Id], 'FULL'); - - await loginPage.loginWith(username); - }); - - beforeEach(async () => { - await page.clickPersonalFilesAndWait(); - await page.dataTable.doubleClickOnRowByName(parent); - }); - - afterAll(async () => { - try { - await userActions.login(username, username); - await userActions.unlockNodes([fileLocked1Id, fileLocked2Id, fileLocked3Id, fileLocked4Id]); - await apis.user.nodes.deleteNodeById(parentId); - await userActions.emptyTrashcan(); - } catch {} - }); - - it('[C217125] delete a file and check notification', async () => { - let items = await page.dataTable.getRowsCount(); - await dataTable.selectItem(file1); - await toolbar.clickMoreActionsDelete(); - const message = await page.getSnackBarMessage(); - expect(message).toContain(`${file1} deleted`); - const action = await page.getSnackBarAction(); - expect(action).toContain('Undo'); - expect(await dataTable.isItemPresent(file1)).toBe(false, `${file1} was not removed from list`); - items--; - expect(await page.pagination.getRange()).toContain(`1-${items} of ${items}`); - await page.clickTrashAndWait(); - expect(await dataTable.isItemPresent(file1)).toBe(true, `${file1} is not in trash`); - }); - - it('[C280502] delete multiple files and check notification', async () => { - let items = await page.dataTable.getRowsCount(); - await dataTable.selectMultipleItems([file2, file3]); - await toolbar.clickMoreActionsDelete(); - const message = await page.getSnackBarMessage(); - expect(message).toContain(`Deleted 2 items`); - expect(await dataTable.isItemPresent(file2)).toBe(false, `${file2} was not removed from list`); - expect(await dataTable.isItemPresent(file3)).toBe(false, `${file3} was not removed from list`); - items = items - 2; - expect(await page.pagination.getRange()).toContain(`1-${items} of ${items}`); - await page.clickTrashAndWait(); - expect(await dataTable.isItemPresent(file2)).toBe(true, `${file2} is not in trash`); - expect(await dataTable.isItemPresent(file3)).toBe(true, `${file3} is not in trash`); - }); - - it('[C217126] delete a folder with content', async () => { - let items = await page.dataTable.getRowsCount(); - await dataTable.selectItem(folder1); - await toolbar.clickMoreActionsDelete(); - expect(await dataTable.isItemPresent(folder1)).toBe(false, `${folder1} was not removed from list`); - items--; - expect(await page.pagination.getRange()).toContain(`1-${items} of ${items}`); - await page.clickTrashAndWait(); - expect(await dataTable.isItemPresent(folder1)).toBe(true, `${folder1} is not in trash`); - expect(await dataTable.isItemPresent(file1InFolder)).toBe(false, `${file1InFolder} is in trash`); - }); - - it('[C217127] delete a folder containing locked files', async () => { - await dataTable.selectItem(folder2); - await toolbar.clickMoreActionsDelete(); - const message = await page.getSnackBarMessage(); - expect(message).toContain(`${folder2} couldn't be deleted`); - const action = await page.getSnackBarAction(); - expect(action).not.toContain('Undo'); - expect(await dataTable.isItemPresent(folder2)).toBe(true, `${folder2} was removed from list`); - await page.clickTrash(); - expect(await dataTable.isItemPresent(folder2)).toBe(false, `${folder2} is in trash`); - expect(await dataTable.isItemPresent(fileLocked1)).toBe(false, `${fileLocked1} is in trash`); - }); - - it('[C217129] notification on multiple items deletion - some items fail to delete', async () => { - await dataTable.selectMultipleItems([file4, folder3]); - await toolbar.clickMoreActionsDelete(); - const message = await page.getSnackBarMessage(); - expect(message).toContain(`Deleted 1 item, 1 couldn't be deleted`); - const action = await page.getSnackBarAction(); - expect(action).toContain('Undo'); - }); - - it('[C217130] notification on multiple items deletion - all items fail to delete', async () => { - await dataTable.selectMultipleItems([folder4, folder5]); - await toolbar.clickMoreActionsDelete(); - const message = await page.getSnackBarMessage(); - expect(message).toEqual(`2 items couldn't be deleted`); - const action = await page.getSnackBarAction(); - expect(action).not.toContain('Undo'); - }); - - it('[C217132] undo delete of file', async () => { - const items = await page.dataTable.getRowsCount(); - - await dataTable.selectItem(file5); - await toolbar.clickMoreActionsDelete(); - - await page.clickSnackBarAction(); - await dataTable.waitForBody(); - expect(await dataTable.isItemPresent(file5)).toBe(true, `${file5} was not restored`); - expect(await page.pagination.getRange()).toContain(`1-${items} of ${items}`); - }); - - it('[C280503] undo delete of folder with content', async () => { - await dataTable.selectItem(folder6); - await toolbar.clickMoreActionsDelete(); - await page.clickSnackBarAction(); - await dataTable.waitForBody(); - expect(await dataTable.isItemPresent(folder6)).toBe(true, `${folder6} was not restored`); - await dataTable.doubleClickOnRowByName(folder6); - await dataTable.waitForBody(); - expect(await dataTable.isItemPresent(file2InFolder)).toBe(true, `${file2InFolder} from ${folder6} not restored`); - }); - - it('[C280504] undo delete of multiple files', async () => { - await dataTable.selectMultipleItems([file6, file7]); - await toolbar.clickMoreActionsDelete(); - await page.clickSnackBarAction(); - await dataTable.waitForBody(); - expect(await dataTable.isItemPresent(file6)).toBe(true, `${file6} was removed from list`); - expect(await dataTable.isItemPresent(file7)).toBe(true, `${file7} was removed from list`); - }); - }); -}); diff --git a/e2e/protractor/suites/actions/delete/permanently-delete.test.ts b/e2e/protractor/suites/actions/delete/permanently-delete.test.ts deleted file mode 100755 index 65fa4006e4..0000000000 --- a/e2e/protractor/suites/actions/delete/permanently-delete.test.ts +++ /dev/null @@ -1,141 +0,0 @@ -/*! - * Copyright © 2005-2024 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 { AdminActions, UserActions, LoginPage, BrowsingPage, ConfirmDialog, RepoClient, Utils, click } from '@alfresco/aca-testing-shared'; - -describe('Permanently delete from Trash', () => { - const username = `user-${Utils.random()}`; - - const file1 = `file1-${Utils.random()}.txt`; - const file2 = `file2-${Utils.random()}.txt`; - const file3 = `file3-${Utils.random()}.txt`; - let filesIds; - - const folder1 = `folder1-${Utils.random()}`; - const folder2 = `folder2-${Utils.random()}`; - let foldersIds; - - const site = `site-${Utils.random()}`; - - const apis = { - user: new RepoClient(username, username) - }; - - const loginPage = new LoginPage(); - const page = new BrowsingPage(); - const { dataTable, toolbar } = page; - - const confirmDialog = new ConfirmDialog(); - const adminApiActions = new AdminActions(); - const userActions = new UserActions(); - - beforeAll(async () => { - await adminApiActions.createUser({ username }); - - filesIds = (await apis.user.nodes.createFiles([file1, file2, file3])).list.entries.map((entries) => entries.entry.id); - foldersIds = (await apis.user.nodes.createFolders([folder1, folder2])).list.entries.map((entries) => entries.entry.id); - await apis.user.sites.createSite(site); - - await userActions.login(username, username); - await apis.user.nodes.deleteNodesById([...filesIds, ...foldersIds], false); - await userActions.deleteSites([site], false); - - await loginPage.loginWith(username); - }); - - beforeEach(async () => { - await page.clickTrashAndWait(); - }); - - afterAll(async () => { - await userActions.login(username, username); - await userActions.emptyTrashcan(); - }); - - it('[C217091] delete a file', async () => { - await dataTable.selectItem(file1); - await click(toolbar.permanentlyDeleteButton); - await page.waitForDialog(); - await click(confirmDialog.deleteButton); - - expect(await page.getSnackBarMessage()).toEqual(`${file1} deleted`); - expect(await dataTable.isItemPresent(file1)).toBe(false, 'Item was not deleted'); - }); - - it('[C280416] delete a folder', async () => { - await dataTable.selectItem(folder1); - await click(toolbar.permanentlyDeleteButton); - await page.waitForDialog(); - await click(confirmDialog.deleteButton); - - expect(await page.getSnackBarMessage()).toEqual(`${folder1} deleted`); - expect(await dataTable.isItemPresent(folder1)).toBe(false, 'Item was not deleted'); - }); - - it('[C290103] delete a library', async () => { - await dataTable.selectItem(site); - await click(toolbar.permanentlyDeleteButton); - await page.waitForDialog(); - await click(confirmDialog.deleteButton); - - expect(await page.getSnackBarMessage()).toEqual(`${site} deleted`); - expect(await dataTable.isItemPresent(site)).toBe(false, `${site} was not deleted`); - }); - - it('[C280417] delete multiple items', async () => { - await dataTable.selectMultipleItems([file2, folder2]); - await click(toolbar.permanentlyDeleteButton); - await page.waitForDialog(); - await click(confirmDialog.deleteButton); - - expect(await page.getSnackBarMessage()).toEqual(`2 items deleted`); - expect(await dataTable.isItemPresent(file2)).toBe(false, 'Item was not deleted'); - expect(await dataTable.isItemPresent(folder2)).toBe(false, 'Item was not deleted'); - }); - - it('[C269113] Confirmation dialog UI', async () => { - await dataTable.selectItem(file3); - await click(toolbar.permanentlyDeleteButton); - await page.waitForDialog(); - - expect(await confirmDialog.isDialogOpen()).toBe(true, 'Confirm delete dialog not open'); - expect(await confirmDialog.getDialogTitle()).toContain('Delete from trash'); - expect(await confirmDialog.getText()).toContain('This will permanently remove the selected item(s)'); - expect(await confirmDialog.isDeleteEnabled()).toBe(true, 'DELETE button is not enabled'); - expect(await confirmDialog.isKeepEnabled()).toBe(true, 'KEEP button is not enabled'); - - await Utils.pressEscape(); - await dataTable.clearSelection(); - }); - - it('[C269115] Keep action cancels the deletion', async () => { - await dataTable.selectItem(file3); - await click(toolbar.permanentlyDeleteButton); - await page.waitForDialog(); - - expect(await confirmDialog.isKeepEnabled()).toBe(true, 'KEEP button is not enabled'); - await click(confirmDialog.keepButton); - expect(await dataTable.isItemPresent(file3)).toBe(true, 'Item was deleted'); - }); -}); diff --git a/e2e/protractor/suites/actions/delete/restore.test.ts b/e2e/protractor/suites/actions/delete/restore.test.ts deleted file mode 100755 index 25516a7630..0000000000 --- a/e2e/protractor/suites/actions/delete/restore.test.ts +++ /dev/null @@ -1,257 +0,0 @@ -/*! - * Copyright © 2005-2024 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, UserActions, LoginPage, BrowsingPage, APP_ROUTES, RepoClient, Utils, click } from '@alfresco/aca-testing-shared'; - -describe('Restore from Trash', () => { - const username = `user-${Utils.random()}`; - - const apis = { - user: new RepoClient(username, username) - }; - - const loginPage = new LoginPage(); - const page = new BrowsingPage(); - const { dataTable, toolbar } = page; - const adminApiActions = new AdminActions(); - const userActions = new UserActions(); - - beforeAll(async () => { - await adminApiActions.createUser({ username }); - await loginPage.loginWith(username); - }); - - afterAll(async () => { - await userActions.login(username, username); - await userActions.emptyTrashcan(); - }); - - describe('successful restore', () => { - const file = `file-${Utils.random()}.txt`; - let fileId: string; - const folder = `folder-${Utils.random()}`; - let folderId: string; - const site = `site-${Utils.random()}`; - - beforeAll(async () => { - fileId = (await apis.user.nodes.createFile(file)).entry.id; - folderId = (await apis.user.nodes.createFolder(folder)).entry.id; - await apis.user.sites.createSite(site); - - await userActions.login(username, username); - await apis.user.nodes.deleteNodesById([fileId, folderId], false); - await userActions.deleteSites([site], false); - }); - - beforeEach(async () => { - await page.clickTrashAndWait(); - }); - - afterAll(async () => { - await userActions.login(username, username); - await userActions.emptyTrashcan(); - }); - - it('[C217177] restore file', async () => { - await dataTable.selectItem(file); - await click(toolbar.restoreButton); - const text = await page.getSnackBarMessage(); - expect(text).toContain(`${file} restored`); - const action = await page.getSnackBarAction(); - expect(action).toContain('View'); - expect(await dataTable.isItemPresent(file)).toBe(false, 'Item was not removed from list'); - await page.clickPersonalFilesAndWait(); - expect(await page.dataTable.isItemPresent(file)).toBe(true, 'Item not displayed in list'); - - await apis.user.nodes.deleteNodeById(fileId, false); - }); - - it('[C280438] restore folder', async () => { - await dataTable.selectItem(folder); - await click(toolbar.restoreButton); - const text = await page.getSnackBarMessage(); - expect(text).toContain(`${folder} restored`); - const action = await page.getSnackBarAction(); - expect(action).toContain('View'); - expect(await dataTable.isItemPresent(folder)).toBe(false, 'Item was not removed from list'); - await page.clickPersonalFilesAndWait(); - expect(await page.dataTable.isItemPresent(folder)).toBe(true, 'Item not displayed in list'); - - await apis.user.nodes.deleteNodeById(folderId, false); - }); - - it('[C290104] restore library', async () => { - await dataTable.selectItem(site); - await click(toolbar.restoreButton); - const text = await page.getSnackBarMessage(); - expect(text).toContain(`${site} restored`); - const action = await page.getSnackBarAction(); - expect(action).toContain('View'); - expect(await dataTable.isItemPresent(site)).toBe(false, `${site} was not removed from list`); - await page.goToMyLibrariesAndWait(); - expect(await page.dataTable.isItemPresent(site)).toBe(true, `${site} not displayed in list`); - }); - - it('[C217182] restore multiple items', async () => { - await dataTable.selectMultipleItems([file, folder]); - await click(toolbar.restoreButton); - const text = await page.getSnackBarMessage(); - expect(text).toContain(`Restore successful`); - const action = await page.getSnackBarAction(); - expect(action).not.toContain('View'); - expect(await dataTable.isItemPresent(file)).toBe(false, 'Item was not removed from list'); - expect(await dataTable.isItemPresent(folder)).toBe(false, 'Item was not removed from list'); - await page.clickPersonalFilesAndWait(); - expect(await page.dataTable.isItemPresent(file)).toBe(true, 'Item not displayed in list'); - expect(await page.dataTable.isItemPresent(folder)).toBe(true, 'Item not displayed in list'); - - await apis.user.nodes.deleteNodesById([fileId, folderId], false); - }); - - it('[C217181] View from notification', async () => { - await dataTable.selectItem(file); - await click(toolbar.restoreButton); - await page.clickSnackBarAction(); - await page.dataTable.waitForHeader(); - expect(await page.sidenav.isActive('Personal Files')).toBe(true, 'Personal Files sidebar link not active'); - expect(await browser.getCurrentUrl()).toContain(APP_ROUTES.PERSONAL_FILES); - - await apis.user.nodes.deleteNodeById(fileId, false); - }); - }); - - describe('failure to restore', () => { - const file1 = `file-${Utils.random()}.txt`; - let file1Id1: string; - let file1Id2: string; - const file2 = `file-${Utils.random()}.txt`; - let file2Id: string; - - const folder1 = `folder-${Utils.random()}`; - let folder1Id: string; - const folder2 = `folder-${Utils.random()}`; - let folder2Id: string; - - beforeAll(async () => { - folder1Id = (await apis.user.nodes.createFolder(folder1)).entry.id; - file1Id1 = (await apis.user.nodes.createFile(file1, folder1Id)).entry.id; - - await userActions.login(username, username); - await apis.user.nodes.deleteNodeById(file1Id1, false); - file1Id2 = (await apis.user.nodes.createFile(file1, folder1Id)).entry.id; - - folder2Id = (await apis.user.nodes.createFolder(folder2)).entry.id; - file2Id = (await apis.user.nodes.createFile(file2, folder2Id)).entry.id; - - await apis.user.nodes.deleteNodesById([file2Id, folder2Id], false); - }); - - beforeEach(async () => { - await page.clickTrashAndWait(); - }); - - afterAll(async () => { - await userActions.login(username, username); - await apis.user.nodes.deleteNodeById(file1Id2); - await userActions.emptyTrashcan(); - }); - - it('[C217178] Restore a file when another file with same name exists on the restore location', async () => { - await dataTable.selectItem(file1); - await click(toolbar.restoreButton); - expect(await page.getSnackBarMessage()).toEqual(`Can't restore, ${file1} already exists`); - }); - - it('[C217179] Restore a file when original location no longer exists', async () => { - await dataTable.selectItem(file2); - await click(toolbar.restoreButton); - expect(await page.getSnackBarMessage()).toEqual(`Can't restore ${file2}, the original location no longer exists`); - }); - }); - - describe('Notification on partial success', () => { - const folder1 = `folder1-${Utils.random()}.txt`; - let folder1Id: string; - const folder2 = `folder2-${Utils.random()}.txt`; - let folder2Id: string; - const file1 = `file-${Utils.random()}.txt`; - let file1Id: string; - const file2 = `file-${Utils.random()}.txt`; - let file2Id: string; - - const folder3 = `folder3-${Utils.random()}.txt`; - let folder3Id: string; - const folder4 = `folder4-${Utils.random()}.txt`; - let folder4Id: string; - const file3 = `file3-${Utils.random()}.txt`; - let file3Id: string; - const file4 = `file4-${Utils.random()}.txt`; - let file4Id: string; - const file5 = `file5-${Utils.random()}.txt`; - let file5Id: string; - - beforeAll(async () => { - try { - folder1Id = (await apis.user.nodes.createFolder(folder1)).entry.id; - file1Id = (await apis.user.nodes.createFile(file1, folder1Id)).entry.id; - folder2Id = (await apis.user.nodes.createFolder(folder2)).entry.id; - file2Id = (await apis.user.nodes.createFile(file2, folder2Id)).entry.id; - - await userActions.login(username, username); - await apis.user.nodes.deleteNodesById([file1Id, folder1Id, file2Id], false); - - folder3Id = (await apis.user.nodes.createFolder(folder3)).entry.id; - file3Id = (await apis.user.nodes.createFile(file3, folder3Id)).entry.id; - file4Id = (await apis.user.nodes.createFile(file4, folder3Id)).entry.id; - folder4Id = (await apis.user.nodes.createFolder(folder4)).entry.id; - file5Id = (await apis.user.nodes.createFile(file5, folder4Id)).entry.id; - - await apis.user.nodes.deleteNodesById([file3Id, file4Id, folder3Id, file5Id], false); - await loginPage.loginWith(username); - } catch {} - }); - - beforeEach(async () => { - await page.clickTrashAndWait(); - }); - - afterAll(async () => { - await userActions.login(username, username); - await userActions.emptyTrashcan(); - }); - - it('[C217183] one failure', async () => { - await dataTable.selectMultipleItems([file1, file2]); - await click(toolbar.restoreButton); - expect(await page.getSnackBarMessage()).toEqual(`Can't restore ${file1}, the original location no longer exists`); - }); - - it('[C217184] multiple failures', async () => { - await dataTable.selectMultipleItems([file3, file4, file5]); - await click(toolbar.restoreButton); - expect(await page.getSnackBarMessage()).toEqual('2 items not restored because of issues with the restore location'); - }); - }); -}); diff --git a/projects/aca-playwright-shared/src/api/nodes-api.ts b/projects/aca-playwright-shared/src/api/nodes-api.ts index 5af6b8fe00..c3260e5635 100755 --- a/projects/aca-playwright-shared/src/api/nodes-api.ts +++ b/projects/aca-playwright-shared/src/api/nodes-api.ts @@ -81,6 +81,15 @@ export class NodesApi { } } + async createFolders(names: string[], relativePath = '/'): Promise { + try { + return await this.createContent({ folders: names }, relativePath); + } catch (error) { + console.error(`${this.constructor.name} ${this.createFolders.name}: ${error}`); + return null; + } + } + async deleteDeletedNode(name: string): Promise { try { await this.apiService.trashCan.deleteDeletedNode(name); @@ -174,6 +183,16 @@ export class NodesApi { } } + async unlockNodes(nodeIds: string[]) { + try { + for (const nodeId of nodeIds) { + await this.apiService.nodes.unlockNode(nodeId); + } + } catch (error) { + console.error(`${this.constructor.name} ${this.unlockNodes.name}`, error); + } + } + async createContent(content: NodeContentTree, relativePath: string = '/'): Promise { try { return await this.apiService.nodes.createNode('-my-', flattenNodeContentTree(content, relativePath) as any); diff --git a/projects/aca-playwright-shared/src/page-objects/components/aca-header.component.ts b/projects/aca-playwright-shared/src/page-objects/components/aca-header.component.ts index b0d7d72c8f..87b524bb6a 100644 --- a/projects/aca-playwright-shared/src/page-objects/components/aca-header.component.ts +++ b/projects/aca-playwright-shared/src/page-objects/components/aca-header.component.ts @@ -42,6 +42,8 @@ export class AcaHeader extends BaseComponent { public uploadButton = this.getChild('button[id="app.toolbar.upload"]'); public uploadFileButton = this.page.locator('button[id="app.create.uploadFile"]'); public uploadInput = this.page.locator('input[id="app-upload-files"]'); + public permanentlyDeleteButton = this.getChild('button[id="app.toolbar.purgeDeletedNodes"]'); + public restoreButton = this.getChild('button[id="app.toolbar.restoreDeletedNodes"]'); constructor(page: Page) { super(page, AcaHeader.rootElement); diff --git a/projects/aca-playwright-shared/src/page-objects/components/dialogs/delete-trash-dialog.component.ts b/projects/aca-playwright-shared/src/page-objects/components/dialogs/delete-trash-dialog.component.ts new file mode 100644 index 0000000000..e238a70c49 --- /dev/null +++ b/projects/aca-playwright-shared/src/page-objects/components/dialogs/delete-trash-dialog.component.ts @@ -0,0 +1,63 @@ +/*! + * Copyright © 2005-2024 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 { Page } from '@playwright/test'; +import { BaseComponent } from '../base.component'; + +export class AdfDeleteTrashComponent extends BaseComponent { + private static rootElement = 'adf-confirm-dialog'; + + constructor(page: Page) { + super(page, AdfDeleteTrashComponent.rootElement); + } + + dialogTitle = this.getChild('[data-automation-id="adf-confirm-dialog-title"]'); + dialogDescription = this.getChild('[data-automation-id="adf-confirm-dialog-base-message"]'); + deleteButton = this.getChild('[id="adf-confirm-accept"]'); + keepButton = this.getChild('[id="adf-confirm-cancel"]'); + + async waitForDialog(): Promise { + await this.dialogTitle.waitFor(); + } + + async isDialogOpen(): Promise { + return this.dialogTitle.isVisible(); + } + + async getDialogTitle(): Promise { + return this.dialogTitle.textContent(); + } + + async getDialogDescription(): Promise { + return this.dialogDescription.textContent(); + } + + async isDeleteEnabled(): Promise { + return this.deleteButton.isEnabled(); + } + + async isKeepEnabled(): Promise { + return this.keepButton.isEnabled(); + } +} diff --git a/projects/aca-playwright-shared/src/page-objects/components/dialogs/index.ts b/projects/aca-playwright-shared/src/page-objects/components/dialogs/index.ts index ca42746bf3..3a01a91737 100644 --- a/projects/aca-playwright-shared/src/page-objects/components/dialogs/index.ts +++ b/projects/aca-playwright-shared/src/page-objects/components/dialogs/index.ts @@ -33,3 +33,4 @@ export * from './share-dialog.component'; export * from './upload-new-version-dialog.component'; export * from './manage-versions-dialog.component'; export * from './upload-dialog.component'; +export * from './delete-trash-dialog.component'; diff --git a/projects/aca-playwright-shared/src/page-objects/components/pagination.component.ts b/projects/aca-playwright-shared/src/page-objects/components/pagination.component.ts index 78dfa25b00..07a3b83d18 100644 --- a/projects/aca-playwright-shared/src/page-objects/components/pagination.component.ts +++ b/projects/aca-playwright-shared/src/page-objects/components/pagination.component.ts @@ -171,6 +171,10 @@ export class PaginationComponent extends BaseComponent { return this.range.isVisible(); } + async getMaxRange(): Promise { + return this.range.textContent(); + } + async isMaxItemsPresent(): Promise { return this.maxItems.isVisible(); } diff --git a/projects/aca-playwright-shared/src/page-objects/components/snackBar/snack-bar.component.ts b/projects/aca-playwright-shared/src/page-objects/components/snackBar/snack-bar.component.ts index 3ff0050af7..ab7e74a932 100644 --- a/projects/aca-playwright-shared/src/page-objects/components/snackBar/snack-bar.component.ts +++ b/projects/aca-playwright-shared/src/page-objects/components/snackBar/snack-bar.component.ts @@ -22,7 +22,7 @@ * from Hyland Software. If not, see . */ -import { Page } from '@playwright/test'; +import { Page, expect } from '@playwright/test'; import { BaseComponent } from '../base.component'; export class SnackBarComponent extends BaseComponent { @@ -30,13 +30,33 @@ export class SnackBarComponent extends BaseComponent { public message = this.getChild('[data-automation-id="adf-snackbar-message-content"]').first(); - public actionButton = this.getChild('[data-automation-id="adf-snackbar-message-content-action-button"]') + public actionButton = this.getChild('[data-automation-id="adf-snackbar-message-content-action-button"]'); public closeIcon = this.getChild('.adf-snackbar-message-content-action-icon'); - public getByMessageLocator = (message: string) => this.getChild(`[data-automation-id='adf-snackbar-message-content']`, - { hasText: message }).first(); + public getByMessageLocator = (message: string) => + this.getChild(`[data-automation-id='adf-snackbar-message-content']`, { hasText: message }).first(); constructor(page: Page, rootElement = SnackBarComponent.rootElement) { super(page, rootElement); } + + async getSnackBarMessage(): Promise { + return this.message.textContent(); + } + + async getSnackBarActionText(): Promise { + if (await this.actionButton.isVisible()){ + return this.actionButton.textContent(); + } else { + return ''; + } + } + + async verifySnackBarActionText(text: string): Promise { + expect(await this.message.textContent()).toContain(text); + } + + async clickSnackBarAction(): Promise { + await this.actionButton.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 92f2fd95d5..dcf7f17dc2 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 @@ -43,7 +43,8 @@ import { AdfInfoDrawerComponent, UploadNewVersionDialog, ManageVersionsDialog, - UploadDialog + UploadDialog, + SnackBarComponent } from '../components'; export class PersonalFilesPage extends BasePage { @@ -72,6 +73,7 @@ export class PersonalFilesPage extends BasePage { public uploadNewVersionDialog = new UploadNewVersionDialog(this.page); public manageVersionsDialog = new ManageVersionsDialog(this.page); public uploadDialog = new UploadDialog(this.page); + public snackBar = new SnackBarComponent(this.page); async selectCreateFolder(): Promise { await this.acaHeader.createButton.click(); diff --git a/projects/aca-playwright-shared/src/page-objects/pages/trash.page.ts b/projects/aca-playwright-shared/src/page-objects/pages/trash.page.ts index 54c2540602..421864aed8 100644 --- a/projects/aca-playwright-shared/src/page-objects/pages/trash.page.ts +++ b/projects/aca-playwright-shared/src/page-objects/pages/trash.page.ts @@ -26,7 +26,7 @@ import { Page } from '@playwright/test'; import { BasePage } from './base.page'; import { DataTableComponent, MatMenuComponent, ViewerComponent, SidenavComponent, Breadcrumb, PaginationComponent } from '../components'; import { AcaHeader } from '../components/aca-header.component'; -import { AdfFolderDialogComponent, ViewerOverlayDialogComponent } from '../components/dialogs'; +import { AdfFolderDialogComponent, ViewerOverlayDialogComponent, AdfDeleteTrashComponent } from '../components/dialogs'; export class TrashPage extends BasePage { private static pageUrl = 'trashcan'; @@ -44,4 +44,5 @@ export class TrashPage extends BasePage { public sidenav = new SidenavComponent(this.page); public breadcrumb = new Breadcrumb(this.page); public pagination = new PaginationComponent(this.page); + public deleteDialog = new AdfDeleteTrashComponent(this.page); }