[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>
This commit is contained in:
Akash Rathod
2026-07-21 15:47:25 +02:00
committed by GitHub
co-authored by Copilot Autofix powered by AI
parent dd2d1f8711
commit ea8eab3986
12 changed files with 932 additions and 0 deletions
@@ -114,6 +114,8 @@ jobs:
id: 19 id: 19
- name: "viewer-actions" - name: "viewer-actions"
id: 20 id: 20
- name: "create-link-actions"
id: 21
steps: steps:
- name: Free hosted runner disk space - name: Free hosted runner disk space
uses: Alfresco/alfresco-build-tools/.github/actions/free-hosted-runner-disk-space@10cea6c0b390c4f8af21d9dc1670d581bd10319f # v18.13.0 uses: Alfresco/alfresco-build-tools/.github/actions/free-hosted-runner-disk-space@10cea6c0b390c4f8af21d9dc1670d581bd10319f # v18.13.0
+2
View File
@@ -187,6 +187,8 @@ jobs:
id: 19 id: 19
- name: "viewer-actions" - name: "viewer-actions"
id: 20 id: 20
- name: "create-link-actions"
id: 21
steps: steps:
- name: Free hosted runner disk space - name: Free hosted runner disk space
uses: Alfresco/alfresco-build-tools/.github/actions/free-hosted-runner-disk-space@10cea6c0b390c4f8af21d9dc1670d581bd10319f # v18.13.0 uses: Alfresco/alfresco-build-tools/.github/actions/free-hosted-runner-disk-space@10cea6c0b390c4f8af21d9dc1670d581bd10319f # v18.13.0
+4
View File
@@ -103,6 +103,10 @@ jobs:
id: 18 id: 18
- name: "folder-information-actions" - name: "folder-information-actions"
id: 19 id: 19
- name: "viewer-actions"
id: 20
- name: "create-link-actions"
id: 21
steps: steps:
- name: Checkout - name: Checkout
uses: actions/checkout@v7 uses: actions/checkout@v7
@@ -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"
}
}
]
}
@@ -0,0 +1,3 @@
{
"all": {}
}
@@ -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 <http://www.gnu.org/licenses/>.
*/
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<CustomConfig> = {
...getGlobalConfig,
grepInvert: getExcludedTestsRegExpArray(EXCLUDED_JSON, 'Create Link Actions'),
projects: createSuiteProjects('Create Link Actions', './src/tests', {}, { timeout: timeouts.extendedTest })
};
export default config;
@@ -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"
]
}
}
}
}
}
@@ -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 <http://www.gnu.org/licenses/>.
*/
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<void> => {
await personalFilesPage.dataTable.selectItems(...itemNames);
await personalFilesPage.clickMoreActionsButton('Create Link');
await personalFilesPage.contentNodeSelector.selectDestination(destinationName);
await personalFilesPage.contentNodeSelector.actionButton.click();
await personalFilesPage.spinnerWaitForReload();
};
});
@@ -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 <http://www.gnu.org/licenses/>.
*/
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<void> => {
await page.navigate({ remoteUrl: `./#/personal-files/${nodeId}`, waitUntil: 'load' });
await page.spinnerWaitForReload();
};
const copyItemInPage = (page: PersonalFilesPage, itemName: string, destinationName: string): Promise<void> =>
performNodeAction(page, itemName, 'Copy', destinationName);
const moveItemInPage = (page: PersonalFilesPage, itemName: string, destinationName: string): Promise<void> =>
performNodeAction(page, itemName, 'Move', destinationName);
const performNodeAction = async (page: PersonalFilesPage, itemName: string, action: 'Copy' | 'Move', destinationName: string): Promise<void> => {
await page.dataTable.selectItems(itemName);
await page.clickMoreActionsButton(action);
await page.contentNodeSelector.searchAndSelectDestination(destinationName);
await page.contentNodeSelector.actionButton.click();
await page.spinnerWaitForReload();
};
});
@@ -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 <http://www.gnu.org/licenses/>.
*/
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();
});
});
@@ -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"]
}
@@ -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"]
}