From ea8eab3986e10aea60bd08df4f81f5c99b488f3f Mon Sep 17 00:00:00 2001 From: Akash Rathod <41251473+akashrathod28@users.noreply.github.com> Date: Tue, 21 Jul 2026 15:47:25 +0200 Subject: [PATCH] [ACS-11871] e2e test for link document (#5281) * [ACS-11871] e2e test for link document * [ACS-11871] fix test case name and improve test with icon check * [ACS-11871] add CI run for e2e and comment fix * [ACS-11871] add CI run for e2e * [ACS-11871] add CI run for e2e * Potential fix for pull request finding Co-authored-by: Copilot Autofix powered by AI <175728472+Copilot@users.noreply.github.com> * Potential fix for pull request finding Co-authored-by: Copilot Autofix powered by AI <175728472+Copilot@users.noreply.github.com> * add fix for comment pr review locator and try catch * [ACS-11871] test fix, remove icon logic to very in manual test * [ACS-11871] e2e test for link document * [ACS-11871] fix test case name and improve test with icon check * [ACS-11871] add CI run for e2e and comment fix * [ACS-11871] add CI run for e2e * [ACS-11871] add CI run for e2e * add fix for comment pr review locator and try catch * Potential fix for pull request finding Co-authored-by: Copilot Autofix powered by AI <175728472+Copilot@users.noreply.github.com> * Potential fix for pull request finding Co-authored-by: Copilot Autofix powered by AI <175728472+Copilot@users.noreply.github.com> * [ACS-11871] test fix, remove icon logic to very in manual test * delete link test fix * fix delete test --------- Co-authored-by: Copilot Autofix powered by AI <175728472+Copilot@users.noreply.github.com> --- .github/workflows/e2e-playwright-reusable.yml | 2 + .github/workflows/pull-request.yml | 2 + .github/workflows/run-e2e-with-env.yml | 4 + .../create-link-actions/.eslintrc.json | 22 ++ .../create-link-actions/exclude.tests.json | 3 + .../create-link-actions/playwright.config.ts | 36 +++ .../create-link-actions/project.json | 131 ++++++++ .../src/tests/create-link.e2e.ts | 134 ++++++++ .../src/tests/link-actions.e2e.ts | 287 ++++++++++++++++++ .../src/tests/link-permissions.e2e.ts | 281 +++++++++++++++++ .../create-link-actions/tsconfig.e2e.adf.json | 15 + .../create-link-actions/tsconfig.e2e.json | 15 + 12 files changed, 932 insertions(+) create mode 100644 e2e/playwright/create-link-actions/.eslintrc.json create mode 100644 e2e/playwright/create-link-actions/exclude.tests.json create mode 100644 e2e/playwright/create-link-actions/playwright.config.ts create mode 100644 e2e/playwright/create-link-actions/project.json create mode 100644 e2e/playwright/create-link-actions/src/tests/create-link.e2e.ts create mode 100644 e2e/playwright/create-link-actions/src/tests/link-actions.e2e.ts create mode 100644 e2e/playwright/create-link-actions/src/tests/link-permissions.e2e.ts create mode 100644 e2e/playwright/create-link-actions/tsconfig.e2e.adf.json create mode 100644 e2e/playwright/create-link-actions/tsconfig.e2e.json diff --git a/.github/workflows/e2e-playwright-reusable.yml b/.github/workflows/e2e-playwright-reusable.yml index 5e0f9761c..ba26846f1 100644 --- a/.github/workflows/e2e-playwright-reusable.yml +++ b/.github/workflows/e2e-playwright-reusable.yml @@ -114,6 +114,8 @@ jobs: id: 19 - name: "viewer-actions" id: 20 + - name: "create-link-actions" + id: 21 steps: - name: Free hosted runner disk space uses: Alfresco/alfresco-build-tools/.github/actions/free-hosted-runner-disk-space@10cea6c0b390c4f8af21d9dc1670d581bd10319f # v18.13.0 diff --git a/.github/workflows/pull-request.yml b/.github/workflows/pull-request.yml index b930f0925..c4a89560c 100644 --- a/.github/workflows/pull-request.yml +++ b/.github/workflows/pull-request.yml @@ -187,6 +187,8 @@ jobs: id: 19 - name: "viewer-actions" id: 20 + - name: "create-link-actions" + id: 21 steps: - name: Free hosted runner disk space uses: Alfresco/alfresco-build-tools/.github/actions/free-hosted-runner-disk-space@10cea6c0b390c4f8af21d9dc1670d581bd10319f # v18.13.0 diff --git a/.github/workflows/run-e2e-with-env.yml b/.github/workflows/run-e2e-with-env.yml index ef8498c64..2083bf490 100644 --- a/.github/workflows/run-e2e-with-env.yml +++ b/.github/workflows/run-e2e-with-env.yml @@ -103,6 +103,10 @@ jobs: id: 18 - name: "folder-information-actions" id: 19 + - name: "viewer-actions" + id: 20 + - name: "create-link-actions" + id: 21 steps: - name: Checkout uses: actions/checkout@v7 diff --git a/e2e/playwright/create-link-actions/.eslintrc.json b/e2e/playwright/create-link-actions/.eslintrc.json new file mode 100644 index 000000000..ea0f5c9b0 --- /dev/null +++ b/e2e/playwright/create-link-actions/.eslintrc.json @@ -0,0 +1,22 @@ +{ + "extends": "../../../.eslintrc.json", + "ignorePatterns": [ + "!**/*" + ], + "overrides": [ + { + "files": [ + "*.ts" + ], + "extends": ["../../../.eslintrc.e2e.json"], + "parserOptions": { + "project": [ + "e2e/playwright/create-link-actions/tsconfig.e2e.json" + ] + }, + "rules": { + "@typescript-eslint/no-floating-promises": "off" + } + } + ] +} diff --git a/e2e/playwright/create-link-actions/exclude.tests.json b/e2e/playwright/create-link-actions/exclude.tests.json new file mode 100644 index 000000000..7507194dc --- /dev/null +++ b/e2e/playwright/create-link-actions/exclude.tests.json @@ -0,0 +1,3 @@ +{ + "all": {} +} diff --git a/e2e/playwright/create-link-actions/playwright.config.ts b/e2e/playwright/create-link-actions/playwright.config.ts new file mode 100644 index 000000000..f9d5f1793 --- /dev/null +++ b/e2e/playwright/create-link-actions/playwright.config.ts @@ -0,0 +1,36 @@ +/*! + * Copyright © 2005-2025 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, timeouts, createSuiteProjects } from '@alfresco/aca-playwright-shared'; +import EXCLUDED_JSON from './exclude.tests.json'; + +const config: PlaywrightTestConfig = { + ...getGlobalConfig, + + grepInvert: getExcludedTestsRegExpArray(EXCLUDED_JSON, 'Create Link Actions'), + projects: createSuiteProjects('Create Link Actions', './src/tests', {}, { timeout: timeouts.extendedTest }) +}; + +export default config; diff --git a/e2e/playwright/create-link-actions/project.json b/e2e/playwright/create-link-actions/project.json new file mode 100644 index 000000000..a45180294 --- /dev/null +++ b/e2e/playwright/create-link-actions/project.json @@ -0,0 +1,131 @@ +{ + "name": "create-link-actions-e2e", + "$schema": "../../../node_modules/nx/schemas/project-schema.json", + "sourceRoot": "e2e/playwright/create-link-actions/src", + "projectType": "application", + "targets": { + "e2e": { + "executor": "nx:run-commands", + "options": { + "commands": [ + "playwright test --config=e2e/playwright/create-link-actions/playwright.config.ts" + ] + }, + "configurations": { + "ui": { + "args": [ + "--ui" + ] + }, + "debug": { + "args": [ + "--debug" + ] + }, + "headed": { + "args": [ + "--headed" + ] + } + } + }, + "lint": { + "executor": "@angular-eslint/builder:lint" + }, + "e2e-multibrowser": { + "executor": "nx:run-commands", + "options": {}, + "configurations": { + "webkit-headed": { + "env": { + "PLAYWRIGHT_BROWSER": "webkit" + }, + "args": [ + "--headed" + ], + "commands": [ + "playwright test --config=e2e/playwright/create-link-actions/playwright.config.ts" + ] + }, + "webkit-headless": { + "env": { + "PLAYWRIGHT_BROWSER": "webkit", + "PLAYWRIGHT_HEADLESS": "true" + }, + "commands": [ + "playwright test --config=e2e/playwright/create-link-actions/playwright.config.ts" + ] + }, + "firefox-headed": { + "env": { + "PLAYWRIGHT_BROWSER": "firefox" + }, + "args": [ + "--headed" + ], + "commands": [ + "playwright test --config=e2e/playwright/create-link-actions/playwright.config.ts" + ] + }, + "firefox-headless": { + "env": { + "PLAYWRIGHT_BROWSER": "firefox", + "PLAYWRIGHT_HEADLESS": "true" + }, + "commands": [ + "playwright test --config=e2e/playwright/create-link-actions/playwright.config.ts" + ] + }, + "chrome-headed": { + "env": { + "PLAYWRIGHT_BROWSER": "chrome" + }, + "args": [ + "--headed" + ], + "commands": [ + "playwright test --config=e2e/playwright/create-link-actions/playwright.config.ts" + ] + }, + "chrome-headless": { + "env": { + "PLAYWRIGHT_BROWSER": "chrome", + "PLAYWRIGHT_HEADLESS": "true" + }, + "commands": [ + "playwright test --config=e2e/playwright/create-link-actions/playwright.config.ts" + ] + }, + "msedge-headed": { + "env": { + "PLAYWRIGHT_BROWSER": "msedge" + }, + "args": [ + "--headed" + ], + "commands": [ + "playwright test --config=e2e/playwright/create-link-actions/playwright.config.ts" + ] + }, + "msedge-headless": { + "env": { + "PLAYWRIGHT_BROWSER": "msedge", + "PLAYWRIGHT_HEADLESS": "true" + }, + "commands": [ + "playwright test --config=e2e/playwright/create-link-actions/playwright.config.ts" + ] + }, + "all-browsers-headless": { + "env": { + "PLAYWRIGHT_BROWSER": "all", + "PLAYWRIGHT_HEADLESS": "true" + }, + "commands": [ + "playwright test --config=e2e/playwright/create-link-actions/playwright.config.ts" + ] + } + } + } + } +} diff --git a/e2e/playwright/create-link-actions/src/tests/create-link.e2e.ts b/e2e/playwright/create-link-actions/src/tests/create-link.e2e.ts new file mode 100644 index 000000000..25b821027 --- /dev/null +++ b/e2e/playwright/create-link-actions/src/tests/create-link.e2e.ts @@ -0,0 +1,134 @@ +/*! + * Copyright © 2005-2025 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 { ApiClientFactory, NodesApi, TrashcanApi, Utils, test, LoginPage, PersonalFilesPage } from '@alfresco/aca-playwright-shared'; +import { expect } from '@playwright/test'; + +test.describe('Create Link - creation scenarios', () => { + let nodesApi: NodesApi; + let trashcanApi: TrashcanApi; + const username = `user-${Utils.random()}`; + + let sourceFile: string; + let sourceFolder: string; + let destinationFolder: string; + + let sourceFileId: string; + let destinationFolderId: string; + + test.beforeAll(async () => { + try { + const apiClientFactory = new ApiClientFactory(); + 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}`); + throw error; + } + }); + + test.beforeEach(async ({ personalFiles, page }) => { + sourceFile = `source-file-${Utils.random()}.txt`; + sourceFolder = `source-folder-${Utils.random()}`; + destinationFolder = `destination-folder-${Utils.random()}`; + + const loginPage = new LoginPage(page); + await Utils.tryLoginUser(loginPage, username, username, 'beforeEach failed'); + + sourceFileId = (await nodesApi.createFile(sourceFile)).entry.id; + await nodesApi.createFolder(sourceFolder); + destinationFolderId = (await nodesApi.createFolder(destinationFolder)).entry.id; + + await personalFiles.navigate(); + }); + + test.afterAll(async () => { + await Utils.deleteNodesSitesEmptyTrashcan(nodesApi, trashcanApi, 'afterAll failed'); + }); + + test('[XAT-19624] Should create a link for a file and verify it in the destination', async ({ personalFiles }) => { + await Utils.reloadPageIfRowNotVisible(personalFiles, sourceFile); + await createLinkInPersonalFiles(personalFiles, [sourceFile], destinationFolder); + + const msg = await personalFiles.snackBar.getSnackBarMessage(); + expect(msg).toContain('Link created for 1 item'); + + await personalFiles.navigate({ remoteUrl: `./#/personal-files/${destinationFolderId}`, waitUntil: 'load' }); + await personalFiles.spinnerWaitForReload(); + + const expectedLinkName = `Link to ${sourceFile}.url`; + expect(await personalFiles.dataTable.isItemPresent(expectedLinkName)).toBe(true); + }); + + test('[XAT-19625] Should create a link for a folder and verify it in the destination', async ({ personalFiles }) => { + await Utils.reloadPageIfRowNotVisible(personalFiles, sourceFolder); + await createLinkInPersonalFiles(personalFiles, [sourceFolder], destinationFolder); + + const msg = await personalFiles.snackBar.getSnackBarMessage(); + expect(msg).toContain('Link created for 1 item'); + + await personalFiles.navigate({ remoteUrl: `./#/personal-files/${destinationFolderId}`, waitUntil: 'load' }); + await personalFiles.spinnerWaitForReload(); + + const expectedLinkName = `Link to ${sourceFolder}.url`; + expect(await personalFiles.dataTable.isItemPresent(expectedLinkName)).toBe(true); + }); + + test('[XAT-19626] Should create links for multiple files and folders simultaneously', async ({ personalFiles }) => { + await Utils.reloadPageIfRowNotVisible(personalFiles, sourceFolder); + await createLinkInPersonalFiles(personalFiles, [sourceFile, sourceFolder], destinationFolder); + + const msg = await personalFiles.snackBar.getSnackBarMessage(); + expect(msg).toContain('Links created for 2 items'); + + await personalFiles.navigate({ remoteUrl: `./#/personal-files/${destinationFolderId}`, waitUntil: 'load' }); + await personalFiles.spinnerWaitForReload(); + + expect(await personalFiles.dataTable.isItemPresent(`Link to ${sourceFile}.url`)).toBe(true); + expect(await personalFiles.dataTable.isItemPresent(`Link to ${sourceFolder}.url`)).toBe(true); + }); + + test('[XAT-19627] Should not allow creating a duplicate link in the same destination', async ({ personalFiles }) => { + await nodesApi.createFileLink(sourceFileId, destinationFolderId); + await Utils.reloadPageIfRowNotVisible(personalFiles, sourceFile); + + await personalFiles.dataTable.selectItems(sourceFile); + await personalFiles.clickMoreActionsButton('Create Link'); + await personalFiles.contentNodeSelector.selectDestination(destinationFolder); + await personalFiles.contentNodeSelector.actionButton.click(); + + const msg = await personalFiles.snackBar.getSnackBarMessage(); + expect(msg).toContain("Can't create a link, a link with the same name already exists in this location."); + }); + + const createLinkInPersonalFiles = async (personalFilesPage: PersonalFilesPage, itemNames: string[], destinationName: string): Promise => { + await personalFilesPage.dataTable.selectItems(...itemNames); + await personalFilesPage.clickMoreActionsButton('Create Link'); + await personalFilesPage.contentNodeSelector.selectDestination(destinationName); + await personalFilesPage.contentNodeSelector.actionButton.click(); + await personalFilesPage.spinnerWaitForReload(); + }; +}); diff --git a/e2e/playwright/create-link-actions/src/tests/link-actions.e2e.ts b/e2e/playwright/create-link-actions/src/tests/link-actions.e2e.ts new file mode 100644 index 000000000..859a256a9 --- /dev/null +++ b/e2e/playwright/create-link-actions/src/tests/link-actions.e2e.ts @@ -0,0 +1,287 @@ +/*! + * Copyright © 2005-2025 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 { ApiClientFactory, NodesApi, TrashcanApi, Utils, test, LoginPage, PersonalFilesPage } from '@alfresco/aca-playwright-shared'; +import { expect } from '@playwright/test'; + +test.describe('Create Link - actions on linked items', () => { + let nodesApi: NodesApi; + let trashcanApi: TrashcanApi; + const username = `user-${Utils.random()}`; + + let sourceFile: string; + let sourceFolder: string; + let destinationFolder: string; + let copyDestinationFolder: string; + let moveDestinationFolder: string; + + let sourceFileId: string; + let sourceFolderId: string; + let destinationFolderId: string; + let copyDestinationFolderId: string; + let moveDestinationFolderId: string; + + let fileLinkName: string; + let folderLinkName: string; + + test.beforeAll(async () => { + try { + const apiClientFactory = new ApiClientFactory(); + 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}`); + throw error; + } + }); + + test.beforeEach(async ({ personalFiles, page }) => { + sourceFile = `source-file-${Utils.random()}.txt`; + sourceFolder = `source-folder-${Utils.random()}`; + destinationFolder = `destination-folder-${Utils.random()}`; + copyDestinationFolder = `copy-dest-${Utils.random()}`; + moveDestinationFolder = `move-dest-${Utils.random()}`; + + const loginPage = new LoginPage(page); + await Utils.tryLoginUser(loginPage, username, username, 'beforeEach failed'); + + sourceFileId = (await nodesApi.createFile(sourceFile)).entry.id; + sourceFolderId = (await nodesApi.createFolder(sourceFolder)).entry.id; + destinationFolderId = (await nodesApi.createFolder(destinationFolder)).entry.id; + copyDestinationFolderId = (await nodesApi.createFolder(copyDestinationFolder)).entry.id; + moveDestinationFolderId = (await nodesApi.createFolder(moveDestinationFolder)).entry.id; + + const fileLinkEntry = await nodesApi.createFileLink(sourceFileId, destinationFolderId); + const folderLinkEntry = await nodesApi.createFolderLink(sourceFolderId, destinationFolderId); + + fileLinkName = fileLinkEntry.entry.name; + folderLinkName = folderLinkEntry.entry.name; + + await personalFiles.navigate(); + await Utils.reloadPageIfRowNotVisible(personalFiles, destinationFolder); + }); + + test.afterAll(async () => { + await Utils.deleteNodesSitesEmptyTrashcan(nodesApi, trashcanApi, 'afterAll failed'); + }); + + test('[XAT-19628] Should copy a linked file to another location and verify the link still exists', async ({ personalFiles }) => { + await navigateToFolderById(personalFiles, destinationFolderId); + await Utils.reloadPageIfRowNotVisible(personalFiles, fileLinkName); + + await copyItemInPage(personalFiles, fileLinkName, copyDestinationFolder); + + const msg = await personalFiles.snackBar.getSnackBarMessage(); + expect(msg).toContain('Copied 1 item'); + + expect(await personalFiles.dataTable.isItemPresent(fileLinkName)).toBe(true); + + await navigateToFolderById(personalFiles, copyDestinationFolderId); + expect(await personalFiles.dataTable.isItemPresent(fileLinkName)).toBe(true); + }); + + test('[XAT-19629] Should copy a linked folder to another location and verify the link still exists', async ({ personalFiles }) => { + await navigateToFolderById(personalFiles, destinationFolderId); + await Utils.reloadPageIfRowNotVisible(personalFiles, folderLinkName); + + await copyItemInPage(personalFiles, folderLinkName, copyDestinationFolder); + + const msg = await personalFiles.snackBar.getSnackBarMessage(); + expect(msg).toContain('Copied 1 item'); + + expect(await personalFiles.dataTable.isItemPresent(folderLinkName)).toBe(true); + + await navigateToFolderById(personalFiles, copyDestinationFolderId); + expect(await personalFiles.dataTable.isItemPresent(folderLinkName)).toBe(true); + }); + + test('[XAT-19630] Should move a linked file to another location and verify the link is accessible', async ({ personalFiles }) => { + await navigateToFolderById(personalFiles, destinationFolderId); + await Utils.reloadPageIfRowNotVisible(personalFiles, fileLinkName); + + await moveItemInPage(personalFiles, fileLinkName, moveDestinationFolder); + + const msg = await personalFiles.snackBar.getSnackBarMessage(); + expect(msg).toContain('Moved 1 item.'); + await personalFiles.snackBar.closeIcon.click(); + + expect(await personalFiles.dataTable.isItemPresent(fileLinkName)).toBe(false); + + await navigateToFolderById(personalFiles, moveDestinationFolderId); + expect(await personalFiles.dataTable.isItemPresent(fileLinkName)).toBe(true); + }); + + test('[XAT-19631] Should move a linked folder to another location and verify the link is accessible', async ({ personalFiles }) => { + await navigateToFolderById(personalFiles, destinationFolderId); + await Utils.reloadPageIfRowNotVisible(personalFiles, folderLinkName); + + await moveItemInPage(personalFiles, folderLinkName, moveDestinationFolder); + + const msg = await personalFiles.snackBar.getSnackBarMessage(); + expect(msg).toContain('Moved 1 item.'); + await personalFiles.snackBar.closeIcon.click(); + + expect(await personalFiles.dataTable.isItemPresent(folderLinkName)).toBe(false); + + await navigateToFolderById(personalFiles, moveDestinationFolderId); + expect(await personalFiles.dataTable.isItemPresent(folderLinkName)).toBe(true); + }); + + test('[XAT-19632] Should permanently delete a linked file without showing Undo or placing it in Trash', async ({ personalFiles, trashPage }) => { + await navigateToFolderById(personalFiles, destinationFolderId); + await Utils.reloadPageIfRowNotVisible(personalFiles, fileLinkName); + + const deleteAction = await personalFiles.dataTable.getActionLocatorFromExpandableMenu(fileLinkName, 'Delete'); + await deleteAction.click(); + await personalFiles.confirmDialog.okButton.click(); + + const msg = await personalFiles.snackBar.getSnackBarMessage(); + expect(msg).toContain('deleted'); + + expect(await personalFiles.snackBar.actionButton.isVisible()).toBe(false); + + await personalFiles.spinnerWaitForReload(); + expect(await personalFiles.dataTable.isItemPresent(fileLinkName)).toBe(false); + + await trashPage.navigate(); + await trashPage.spinnerWaitForReload(); + expect(await trashPage.dataTable.isItemPresent(fileLinkName)).toBe(false); + }); + + test('[XAT-19633] Should permanently delete a linked folder without showing Undo or placing it in Trash', async ({ personalFiles, trashPage }) => { + await navigateToFolderById(personalFiles, destinationFolderId); + await Utils.reloadPageIfRowNotVisible(personalFiles, folderLinkName); + + const deleteAction = await personalFiles.dataTable.getActionLocatorFromExpandableMenu(folderLinkName, 'Delete'); + await deleteAction.click(); + await personalFiles.confirmDialog.okButton.click(); + + const msg = await personalFiles.snackBar.getSnackBarMessage(); + expect(msg).toContain('deleted'); + + expect(await personalFiles.snackBar.actionButton.isVisible()).toBe(false); + + await personalFiles.spinnerWaitForReload(); + expect(await personalFiles.dataTable.isItemPresent(folderLinkName)).toBe(false); + + await trashPage.navigate(); + await trashPage.spinnerWaitForReload(); + expect(await trashPage.dataTable.isItemPresent(folderLinkName)).toBe(false); + }); + + test('[XAT-19634] Should display N/A for size in the info drawer of a linked file', async ({ personalFiles }) => { + await navigateToFolderById(personalFiles, destinationFolderId); + await Utils.reloadPageIfRowNotVisible(personalFiles, fileLinkName); + + await personalFiles.dataTable.selectItems(fileLinkName); + await personalFiles.acaHeader.clickMoreActions(); + await personalFiles.matMenu.clickMenuItem('Information'); + + await expect(personalFiles.folderInformationDialog.folderSize).toBeVisible(); + const sizeText = await personalFiles.folderInformationDialog.folderSize.textContent(); + expect(sizeText?.trim()).toBe('N/A'); + + await personalFiles.folderInformationDialog.doneButton.click(); + }); + + test('[XAT-19635] Should display N/A for size in the info drawer of a linked folder', async ({ personalFiles }) => { + await navigateToFolderById(personalFiles, destinationFolderId); + await Utils.reloadPageIfRowNotVisible(personalFiles, folderLinkName); + + await personalFiles.dataTable.selectItems(folderLinkName); + await personalFiles.acaHeader.clickMoreActions(); + await personalFiles.matMenu.clickMenuItem('Information'); + + await expect(personalFiles.folderInformationDialog.folderSize).toBeVisible(); + const sizeText = await personalFiles.folderInformationDialog.folderSize.textContent(); + expect(sizeText?.trim()).toBe('N/A'); + + await personalFiles.folderInformationDialog.doneButton.click(); + }); + + test('[XAT-19636] Should locate the original file when using Locate Linked Item on a file link', async ({ personalFiles }) => { + await navigateToFolderById(personalFiles, destinationFolderId); + await Utils.reloadPageIfRowNotVisible(personalFiles, fileLinkName); + + await personalFiles.dataTable.performActionFromExpandableMenu(fileLinkName, 'Locate Linked Item'); + await personalFiles.spinnerWaitForReload(); + + await personalFiles.page.waitForURL('**/#/personal-files**'); + expect(await personalFiles.dataTable.isItemPresent(sourceFile)).toBe(true); + }); + + test('[XAT-19637] Should locate the original folder when using Locate Linked Item on a folder link', async ({ personalFiles }) => { + await navigateToFolderById(personalFiles, destinationFolderId); + await Utils.reloadPageIfRowNotVisible(personalFiles, folderLinkName); + + await personalFiles.dataTable.performActionFromExpandableMenu(folderLinkName, 'Locate Linked Item'); + await personalFiles.spinnerWaitForReload(); + + await personalFiles.page.waitForURL('**/#/personal-files**'); + expect(await personalFiles.dataTable.isItemPresent(sourceFolder)).toBe(true); + }); + + test('[XAT-19638] Should navigate to original file location when opening a file link', async ({ personalFiles }) => { + await navigateToFolderById(personalFiles, destinationFolderId); + await Utils.reloadPageIfRowNotVisible(personalFiles, fileLinkName); + + await personalFiles.dataTable.performClickFolderOrFileToOpen(fileLinkName); + await personalFiles.spinnerWaitForReload(); + + await personalFiles.page.waitForURL('**/#/personal-files**'); + expect(await personalFiles.dataTable.isItemPresent(sourceFile)).toBe(true); + }); + + test('[XAT-19639] Should navigate inside the original folder when opening a folder link', async ({ personalFiles }) => { + await navigateToFolderById(personalFiles, destinationFolderId); + await Utils.reloadPageIfRowNotVisible(personalFiles, folderLinkName); + + await personalFiles.dataTable.performClickFolderOrFileToOpen(folderLinkName); + await personalFiles.spinnerWaitForReload(); + + await personalFiles.page.waitForURL(`**/${sourceFolderId}**`); + await expect(personalFiles.breadcrumb.currentItem).toHaveText(sourceFolder); + }); + + const navigateToFolderById = async (page: PersonalFilesPage, nodeId: string): Promise => { + await page.navigate({ remoteUrl: `./#/personal-files/${nodeId}`, waitUntil: 'load' }); + await page.spinnerWaitForReload(); + }; + + const copyItemInPage = (page: PersonalFilesPage, itemName: string, destinationName: string): Promise => + performNodeAction(page, itemName, 'Copy', destinationName); + + const moveItemInPage = (page: PersonalFilesPage, itemName: string, destinationName: string): Promise => + performNodeAction(page, itemName, 'Move', destinationName); + + const performNodeAction = async (page: PersonalFilesPage, itemName: string, action: 'Copy' | 'Move', destinationName: string): Promise => { + await page.dataTable.selectItems(itemName); + await page.clickMoreActionsButton(action); + await page.contentNodeSelector.searchAndSelectDestination(destinationName); + await page.contentNodeSelector.actionButton.click(); + await page.spinnerWaitForReload(); + }; +}); diff --git a/e2e/playwright/create-link-actions/src/tests/link-permissions.e2e.ts b/e2e/playwright/create-link-actions/src/tests/link-permissions.e2e.ts new file mode 100644 index 000000000..8405eabfd --- /dev/null +++ b/e2e/playwright/create-link-actions/src/tests/link-permissions.e2e.ts @@ -0,0 +1,281 @@ +/*! + * Copyright © 2005-2025 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 { ApiClientFactory, NodesApi, SitesApi, TrashcanApi, Utils, test, LoginPage, PersonalFilesPage } from '@alfresco/aca-playwright-shared'; +import { Site } from '@alfresco/js-api'; +import { expect } from '@playwright/test'; + +test.describe('Create Link - comments and permissions', () => { + let ownerNodesApi: NodesApi; + let consumerNodesApi: NodesApi; + let ownerTrashcanApi: TrashcanApi; + let consumerTrashcanApi: TrashcanApi; + let sitesApi: SitesApi; + + const random = Utils.random(); + const ownerUsername = `owner-${random}`; + const consumerUsername = `consumer-${random}`; + + let siteId: string; + let siteTitle: string; + let docLibId: string; + let sourceFile: string; + let sourceFolder: string; + let destinationFolder: string; + + let sourceFileId: string; + let destinationFolderId: string; + + let fileLinkName: string; + let folderLinkName: string; + + test.beforeAll(async () => { + try { + const apiClientFactory = new ApiClientFactory(); + await apiClientFactory.setUpAcaBackend('admin'); + await apiClientFactory.createUser({ username: ownerUsername }); + await apiClientFactory.createUser({ username: consumerUsername }); + + ownerNodesApi = await NodesApi.initialize(ownerUsername, ownerUsername); + consumerNodesApi = await NodesApi.initialize(consumerUsername, consumerUsername); + ownerTrashcanApi = await TrashcanApi.initialize(ownerUsername, ownerUsername); + consumerTrashcanApi = await TrashcanApi.initialize(consumerUsername, consumerUsername); + sitesApi = await SitesApi.initialize(ownerUsername, ownerUsername); + + const site = await sitesApi.createSite(`link-perm-site-${random}`, Site.VisibilityEnum.PRIVATE); + siteId = site.entry.id; + siteTitle = site.entry.title; + docLibId = await sitesApi.getDocLibId(siteId); + await sitesApi.addSiteMember(siteId, consumerUsername, 'SiteConsumer'); + } catch (error) { + console.error(`beforeAll failed: ${error}`); + throw error; + } + }); + + test.beforeEach(async ({ personalFiles, page }) => { + try { + sourceFile = `source-file-${Utils.random()}.txt`; + sourceFolder = `source-folder-${Utils.random()}`; + destinationFolder = `destination-folder-${Utils.random()}`; + + const loginPage = new LoginPage(page); + await Utils.tryLoginUser(loginPage, ownerUsername, ownerUsername, 'beforeEach failed'); + + sourceFileId = (await ownerNodesApi.createFile(sourceFile)).entry.id; + const sourceFolderEntry = await ownerNodesApi.createFolder(sourceFolder); + destinationFolderId = (await ownerNodesApi.createFolder(destinationFolder)).entry.id; + + const fileLinkEntry = await ownerNodesApi.createFileLink(sourceFileId, destinationFolderId); + const folderLinkEntry = await ownerNodesApi.createFolderLink(sourceFolderEntry.entry.id, destinationFolderId); + + fileLinkName = fileLinkEntry.entry.name; + folderLinkName = folderLinkEntry.entry.name; + + await personalFiles.navigate(); + } catch (error) { + console.error(`beforeEach failed: ${error}`); + throw error; + } + }); + + test.afterAll(async () => { + let primaryError: unknown; + + try { + await Utils.deleteNodesSitesEmptyTrashcan(consumerNodesApi, consumerTrashcanApi, 'afterAll failed (consumer cleanup)'); + } catch (error) { + console.error(`afterAll failed (consumer cleanup): ${error}`); + primaryError = error; + } + + try { + await Utils.deleteNodesSitesEmptyTrashcan(ownerNodesApi, ownerTrashcanApi, 'afterAll failed', sitesApi, [siteId]); + } catch (error) { + console.error(`afterAll failed: ${error}`); + primaryError ??= error; + } + + if (primaryError) { + throw primaryError; + } + }); + + test('[XAT-19640] Should show a comment added on a linked file in the original file info drawer', async ({ personalFiles }) => { + const commentText = `comment-${Utils.random()}`; + + await personalFiles.navigate({ remoteUrl: `./#/personal-files/${destinationFolderId}`, waitUntil: 'load' }); + await personalFiles.spinnerWaitForReload(); + await Utils.reloadPageIfRowNotVisible(personalFiles, fileLinkName); + + await personalFiles.dataTable.getRowByName(fileLinkName).click(); + await personalFiles.acaHeader.viewDetails.click(); + await personalFiles.infoDrawer.commentsTab.click(); + await expect(personalFiles.infoDrawer.commentInputField).toBeVisible(); + await personalFiles.infoDrawer.addCommentToNode(commentText); + + await personalFiles.infoDrawer.waitForComments(); + + await personalFiles.navigate({ remoteUrl: `./#/personal-files`, waitUntil: 'load' }); + await personalFiles.spinnerWaitForReload(); + await Utils.reloadPageIfRowNotVisible(personalFiles, sourceFile); + + await personalFiles.dataTable.getRowByName(sourceFile).click(); + await personalFiles.acaHeader.viewDetails.click(); + await personalFiles.infoDrawer.commentsTab.click(); + await personalFiles.infoDrawer.waitForComments(); + + const commentsCount = await personalFiles.infoDrawer.getCommentsCountFromList(); + expect(commentsCount).toBeGreaterThanOrEqual(1); + + const commentContent = await personalFiles.infoDrawer.commentTextContent.first().innerText(); + expect(commentContent).toContain(commentText); + }); + + test('[XAT-19641] Should show a comment added on a linked folder in the original folder info drawer', async ({ personalFiles }) => { + const commentText = `comment-${Utils.random()}`; + + await personalFiles.navigate({ remoteUrl: `./#/personal-files/${destinationFolderId}`, waitUntil: 'load' }); + await personalFiles.spinnerWaitForReload(); + await Utils.reloadPageIfRowNotVisible(personalFiles, folderLinkName); + + await personalFiles.dataTable.getRowByName(folderLinkName).click(); + await personalFiles.acaHeader.viewDetails.click(); + await personalFiles.infoDrawer.commentsTab.click(); + await expect(personalFiles.infoDrawer.commentInputField).toBeVisible(); + await personalFiles.infoDrawer.addCommentToNode(commentText); + + await personalFiles.infoDrawer.waitForComments(); + + await personalFiles.navigate({ remoteUrl: `./#/personal-files`, waitUntil: 'load' }); + await personalFiles.spinnerWaitForReload(); + await Utils.reloadPageIfRowNotVisible(personalFiles, sourceFolder); + + await personalFiles.dataTable.getRowByName(sourceFolder).click(); + await personalFiles.acaHeader.viewDetails.click(); + await personalFiles.infoDrawer.commentsTab.click(); + await personalFiles.infoDrawer.waitForComments(); + + const commentsCount = await personalFiles.infoDrawer.getCommentsCountFromList(); + expect(commentsCount).toBeGreaterThanOrEqual(1); + + const commentContent = await personalFiles.infoDrawer.commentTextContent.first().innerText(); + expect(commentContent).toContain(commentText); + }); + + test('[XAT-19642] Should show Create Link action for manager and successfully create a link from a site document library', async ({ + personalFiles + }) => { + const siteFile = `site-file-${Utils.random()}.txt`; + const managerDestFolder = `manager-dest-${Utils.random()}`; + + await ownerNodesApi.createFile(siteFile, docLibId); + const managerDestFolderEntry = await ownerNodesApi.createFolder(managerDestFolder); + const managerDestFolderId = managerDestFolderEntry.entry.id; + + await personalFiles.navigate({ remoteUrl: `./#/libraries/${docLibId}`, waitUntil: 'load' }); + await personalFiles.spinnerWaitForReload(); + await Utils.reloadPageIfRowNotVisible(personalFiles, siteFile); + + await personalFiles.dataTable.rightClickOnItem(siteFile); + const createLinkButton = personalFiles.dataTable.contextMenuActions.getButtonByText('Create Link'); + await expect(createLinkButton).toBeVisible(); + await createLinkButton.click(); + + await personalFiles.contentNodeSelector.searchAndSelectDestination(managerDestFolder); + await personalFiles.contentNodeSelector.actionButton.click(); + + const msg = await personalFiles.snackBar.getSnackBarMessage(); + expect(msg).toContain('Link created for 1 item'); + + await personalFiles.navigate({ remoteUrl: `./#/personal-files/${managerDestFolderId}`, waitUntil: 'load' }); + await personalFiles.spinnerWaitForReload(); + + const expectedLinkName = `Link to ${siteFile}.url`; + expect(await personalFiles.dataTable.isItemPresent(expectedLinkName)).toBe(true); + }); + + test('[XAT-19643] Should allow a consumer to create a link to a site file in their own personal files', async ({ personalFiles, page }) => { + const siteFile = `site-file-consumer-${Utils.random()}.txt`; + const consumerDestFolder = `consumer-dest-${Utils.random()}`; + + await ownerNodesApi.createFile(siteFile, docLibId); + const consumerDestFolderEntry = await consumerNodesApi.createFolder(consumerDestFolder); + const consumerDestFolderId = consumerDestFolderEntry.entry.id; + + const loginPage = new LoginPage(page); + await loginPage.logoutUser(); + await loginPage.loginUser({ username: consumerUsername, password: consumerUsername }, { withNavigation: true, waitForLoading: true }); + + await personalFiles.navigate({ remoteUrl: `./#/libraries/${docLibId}`, waitUntil: 'load' }); + await personalFiles.spinnerWaitForReload(); + await Utils.reloadPageIfRowNotVisible(personalFiles, siteFile); + + await personalFiles.dataTable.rightClickOnItem(siteFile); + const createLinkButton = personalFiles.dataTable.contextMenuActions.getButtonByText('Create Link'); + await expect(createLinkButton).toBeVisible(); + await createLinkButton.click(); + + await personalFiles.contentNodeSelector.searchAndSelectDestination(consumerDestFolder); + await personalFiles.contentNodeSelector.actionButton.click(); + + const msg = await personalFiles.snackBar.getSnackBarMessage(); + expect(msg).toContain('Link created for 1 item'); + + await personalFiles.navigate({ remoteUrl: `./#/personal-files/${consumerDestFolderId}`, waitUntil: 'load' }); + await personalFiles.spinnerWaitForReload(); + + const expectedLinkName = `Link to ${siteFile}.url`; + expect(await personalFiles.dataTable.isItemPresent(expectedLinkName)).toBe(true); + }); + + test('[XAT-19644] Should disable the Choose button in the node selector when consumer selects a read-only library folder', async ({ page }) => { + const siteFile = `site-file-ro-${Utils.random()}.txt`; + await ownerNodesApi.createFile(siteFile, docLibId); + + const loginPage = new LoginPage(page); + await loginPage.logoutUser(); + await loginPage.loginUser({ username: consumerUsername, password: consumerUsername }, { withNavigation: true, waitForLoading: true }); + + const consumerPage = new PersonalFilesPage(page); + + await consumerPage.navigate({ remoteUrl: `./#/libraries/${docLibId}`, waitUntil: 'load' }); + await consumerPage.spinnerWaitForReload(); + await Utils.reloadPageIfRowNotVisible(consumerPage, siteFile); + + await consumerPage.dataTable.selectItems(siteFile); + await consumerPage.acaHeader.clickMoreActions(); + const createLinkMenuItem = consumerPage.matMenu.getButtonByText('Create Link'); + await expect(createLinkMenuItem).toBeVisible(); + await createLinkMenuItem.click(); + + await consumerPage.contentNodeSelector.selectLocation('My Libraries'); + await consumerPage.contentNodeSelector.spinnerWaitForReload(); + + await consumerPage.contentNodeSelector.selectDestination(siteTitle); + expect(await consumerPage.contentNodeSelector.actionButton.isDisabled()).toBe(true); + + await consumerPage.contentNodeSelector.cancelButton.click(); + }); +}); diff --git a/e2e/playwright/create-link-actions/tsconfig.e2e.adf.json b/e2e/playwright/create-link-actions/tsconfig.e2e.adf.json new file mode 100644 index 000000000..47ea883b4 --- /dev/null +++ b/e2e/playwright/create-link-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/aca-playwright-shared": ["dist/@alfresco/aca-playwright-shared"] + } + }, + "exclude": ["node_modules"] +} diff --git a/e2e/playwright/create-link-actions/tsconfig.e2e.json b/e2e/playwright/create-link-actions/tsconfig.e2e.json new file mode 100644 index 000000000..16d87d8ec --- /dev/null +++ b/e2e/playwright/create-link-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/aca-playwright-shared": ["dist/@alfresco/aca-playwright-shared"] + } + }, + "exclude": ["node_modules"] +}