diff --git a/.github/workflows/pull-request.yml b/.github/workflows/pull-request.yml
index 1f9fcfdc5..9372a8e11 100644
--- a/.github/workflows/pull-request.yml
+++ b/.github/workflows/pull-request.yml
@@ -148,6 +148,8 @@ jobs:
id: 17
- name: "smoke-test"
id: 18
+ - name: "folder-information-actions"
+ id: 19
steps:
- name: Checkout
uses: actions/checkout@v4
diff --git a/e2e/playwright/folder-information-actions/.eslintrc.json b/e2e/playwright/folder-information-actions/.eslintrc.json
new file mode 100644
index 000000000..eaa4081d9
--- /dev/null
+++ b/e2e/playwright/folder-information-actions/.eslintrc.json
@@ -0,0 +1,26 @@
+{
+ "extends": "../../../.eslintrc.json",
+ "ignorePatterns": [
+ "!**/*"
+ ],
+ "overrides": [
+ {
+ "files": [
+ "*.ts"
+ ],
+ "parserOptions": {
+ "project": [
+ "e2e/playwright/folder-information-actions/tsconfig.e2e.json"
+ ],
+ "createDefaultProgram": true
+ },
+ "plugins": [
+ "rxjs",
+ "unicorn"
+ ],
+ "rules": {
+ "@typescript-eslint/no-floating-promises": "off"
+ }
+ }
+ ]
+}
diff --git a/e2e/playwright/folder-information-actions/exclude.tests.json b/e2e/playwright/folder-information-actions/exclude.tests.json
new file mode 100644
index 000000000..0967ef424
--- /dev/null
+++ b/e2e/playwright/folder-information-actions/exclude.tests.json
@@ -0,0 +1 @@
+{}
diff --git a/e2e/playwright/folder-information-actions/playwright.config.ts b/e2e/playwright/folder-information-actions/playwright.config.ts
new file mode 100644
index 000000000..af7e4b0cd
--- /dev/null
+++ b/e2e/playwright/folder-information-actions/playwright.config.ts
@@ -0,0 +1,42 @@
+/*!
+ * 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 } from '@alfresco/aca-playwright-shared';
+import EXCLUDED_JSON from './exclude.tests.json';
+
+const config: PlaywrightTestConfig = {
+ ...getGlobalConfig,
+
+ grepInvert: getExcludedTestsRegExpArray(EXCLUDED_JSON, 'Folder Information Actions'),
+ projects: [
+ {
+ name: 'Folder Information Actions',
+ testDir: './src/tests',
+ use: {}
+ }
+ ]
+};
+
+export default config;
diff --git a/e2e/playwright/folder-information-actions/project.json b/e2e/playwright/folder-information-actions/project.json
new file mode 100644
index 000000000..4671dbb2e
--- /dev/null
+++ b/e2e/playwright/folder-information-actions/project.json
@@ -0,0 +1,31 @@
+{
+ "name": "folder-information-actions-e2e",
+ "$schema": "../../../node_modules/nx/schemas/project-schema.json",
+ "sourceRoot": "e2e/playwright/folder-information-actions",
+ "projectType": "application",
+ "targets": {
+ "e2e": {
+ "executor": "nx:run-commands",
+ "options": {
+ "commands": ["npx playwright test --config=e2e/playwright/folder-information-actions/playwright.config.ts"]
+ },
+ "configurations": {
+ "production": {
+ "devServerTarget": "content-ce:serve:production"
+ },
+ "ui": {
+ "args": ["--ui"]
+ },
+ "debug": {
+ "args": ["--debug"]
+ },
+ "headed": {
+ "args": ["--headed"]
+ }
+ }
+ },
+ "lint": {
+ "executor": "@angular-eslint/builder:lint"
+ }
+ }
+}
diff --git a/e2e/playwright/folder-information-actions/src/tests/folder-information.e2e.ts b/e2e/playwright/folder-information-actions/src/tests/folder-information.e2e.ts
new file mode 100644
index 000000000..16b774413
--- /dev/null
+++ b/e2e/playwright/folder-information-actions/src/tests/folder-information.e2e.ts
@@ -0,0 +1,195 @@
+/*!
+ * 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 { expect } from '@playwright/test';
+import {
+ ApiClientFactory,
+ NodesApi,
+ Utils,
+ test,
+ TrashcanApi,
+ TEST_FILES,
+ FileActionsApi,
+ PersonalFilesPage,
+ MyLibrariesPage,
+ SitesApi,
+ SearchPage
+} from '@alfresco/aca-playwright-shared';
+
+test.describe('Actions - Folder Information', () => {
+ const username = `user-e2e-${Utils.random()}`;
+ const emptyFolder = `folder17722-${Utils.random()}`;
+ const folder1File = `folder17715-${Utils.random()}`;
+ const folderXFiles = `folder17752-${Utils.random()}`;
+ const folderXFilesAndFolders = `folder17753-1-${Utils.random()}`;
+ const folderInsideFolder = `folder17753-2-${Utils.random()}`;
+ const folderInLibrary = `folder17758-${Utils.random()}`;
+ const libraryForFolder = `library17758-${Utils.random()}`;
+ const folderForSearch = `folder17759-${Utils.random()}`;
+ const folderNested1 = `folder17766-1-${Utils.random()}`;
+ const folderNested2 = `folder17766-1-${Utils.random()}`;
+ const folderNested3 = `folder17766-1-${Utils.random()}`;
+ const file1 = `file1-${Utils.random()}.docx`;
+ const file2 = `file2-${Utils.random()}.docx`;
+ const file3 = `file3-${Utils.random()}.docx`;
+ const file4 = `file4-${Utils.random()}.docx`;
+ const file5 = `file5-${Utils.random()}.docx`;
+ const file6 = `file6-${Utils.random()}.docx`;
+ const file7 = `file7-${Utils.random()}.docx`;
+ const file8 = `file8-${Utils.random()}.docx`;
+ const file9 = `file9-${Utils.random()}.docx`;
+ const file10 = `file10-${Utils.random()}.docx`;
+ const file11 = `file10-${Utils.random()}.docx`;
+ const file12 = `file10-${Utils.random()}.docx`;
+ const file13 = `file10-${Utils.random()}.docx`;
+ let nodesApi: NodesApi;
+ let trashcanApi: TrashcanApi;
+ let fileActionsApi: FileActionsApi;
+ let sitesApi: SitesApi;
+ let folder1FileId: string;
+ let folderXFilesId: string;
+ let folderXFilesAndFoldersId: string;
+ let folderInsideFolderId: string;
+ let folderInLibraryId: string;
+ let siteId: string;
+ let folderForSearchId: string;
+ let folderNested1Id: string;
+ let folderNested2Id: string;
+ let folderNested3Id: string;
+
+ test.beforeAll(async () => {
+ const apiService = new ApiClientFactory();
+ await apiService.setUpAcaBackend('admin');
+ await apiService.createUser({ username: username });
+ nodesApi = await NodesApi.initialize(username, username);
+ trashcanApi = await TrashcanApi.initialize(username, username);
+ fileActionsApi = await FileActionsApi.initialize(username, username);
+ sitesApi = await SitesApi.initialize(username, username);
+
+ siteId = (await sitesApi.createSite(libraryForFolder)).entry.id;
+ const docLibId = await sitesApi.getDocLibId(siteId);
+ await nodesApi.createFolder(emptyFolder, '-my-');
+ folder1FileId = (await nodesApi.createFolder(folder1File, '-my-')).entry.id;
+ folderXFilesId = (await nodesApi.createFolder(folderXFiles, '-my-')).entry.id;
+ folderXFilesAndFoldersId = (await nodesApi.createFolder(folderXFilesAndFolders, '-my-')).entry.id;
+ folderInsideFolderId = (await nodesApi.createFolder(folderInsideFolder, folderXFilesAndFoldersId)).entry.id;
+ folderInLibraryId = (await nodesApi.createFolder(folderInLibrary, docLibId)).entry.id;
+ folderForSearchId = (await nodesApi.createFolder(folderForSearch, '-my-')).entry.id;
+ folderNested1Id = (await nodesApi.createFolder(folderNested1, '-my-')).entry.id;
+ folderNested2Id = (await nodesApi.createFolder(folderNested2, folderNested1Id)).entry.id;
+ folderNested3Id = (await nodesApi.createFolder(folderNested3, folderNested2Id)).entry.id;
+
+ await fileActionsApi.uploadFileWithRename(TEST_FILES.DOCX.path, file1, folder1FileId);
+ await fileActionsApi.uploadFileWithRename(TEST_FILES.DOCX.path, file2, folderXFilesId);
+ await fileActionsApi.uploadFileWithRename(TEST_FILES.DOCX.path, file3, folderXFilesId);
+ await fileActionsApi.uploadFileWithRename(TEST_FILES.DOCX.path, file4, folderXFilesId);
+ await fileActionsApi.uploadFileWithRename(TEST_FILES.DOCX.path, file5, folderXFilesAndFoldersId);
+ await fileActionsApi.uploadFileWithRename(TEST_FILES.DOCX.path, file6, folderXFilesAndFoldersId);
+ await fileActionsApi.uploadFileWithRename(TEST_FILES.DOCX.path, file7, folderXFilesAndFoldersId);
+ await fileActionsApi.uploadFileWithRename(TEST_FILES.DOCX.path, file8, folderInsideFolderId);
+ await fileActionsApi.uploadFileWithRename(TEST_FILES.DOCX.path, file9, folderInsideFolderId);
+ await fileActionsApi.uploadFileWithRename(TEST_FILES.DOCX.path, file10, folderInLibraryId);
+ await fileActionsApi.uploadFileWithRename(TEST_FILES.DOCX.path, file11, folderForSearchId);
+ await fileActionsApi.uploadFileWithRename(TEST_FILES.DOCX.path, file12, folderForSearchId);
+ await fileActionsApi.uploadFileWithRename(TEST_FILES.DOCX.path, file13, folderNested3Id);
+ });
+
+ test.beforeEach(async ({ loginPage }) => {
+ await Utils.tryLoginUser(loginPage, username, username, 'Main beforeEach failed');
+ });
+
+ test.afterAll(async () => {
+ await Utils.deleteNodesSitesEmptyTrashcan(nodesApi, trashcanApi, 'afterAll failed');
+ });
+
+ async function checkFolderInformation(
+ page: PersonalFilesPage | MyLibrariesPage | SearchPage,
+ folderName: string,
+ expectedSize: string,
+ location: string,
+ isEmptyFolder?: 'isEmpty'
+ ) {
+ await page.dataTable.selectItems(folderName);
+ await page.acaHeader.clickMoreActions();
+ await page.matMenu.clickMenuItem('Folder Information');
+ await expect(async () => {
+ expect(await page.folderInformationDialog.folderSize.textContent()).toContain(expectedSize);
+ }).toPass({
+ intervals: [1_000],
+ timeout: 5_000
+ });
+ if (!isEmptyFolder) {
+ expect(await page.folderInformationDialog.getFolderSizeNumber()).toBeGreaterThan(0);
+ }
+ expect(await page.folderInformationDialog.folderLocation.textContent()).toContain(location);
+ expect(await page.folderInformationDialog.folderCreationDate.textContent()).toContain('ago');
+ expect(await page.folderInformationDialog.folderModifiedDate.textContent()).toContain('ago');
+ await page.folderInformationDialog.doneButton.click();
+ await page.folderInformationDialog.folderName.waitFor({ state: 'hidden' });
+ }
+
+ test('[XAT-17722] Folder information Empty folder size and number of documents as 0', async ({ personalFiles }) => {
+ await personalFiles.navigate();
+ await checkFolderInformation(personalFiles, emptyFolder, '0 bytes for 0 files', `/Company Home/User Homes/${username}`, 'isEmpty');
+ });
+
+ test('[XAT-17715] Folder information correct folder size and number of documents - single file', async ({ personalFiles }) => {
+ await personalFiles.navigate();
+ await checkFolderInformation(personalFiles, folder1File, 'for 1 files', `/Company Home/User Homes/${username}`);
+ });
+
+ test('[XAT-17752] Folder information correct folder size and number of documents - multiple files', async ({ personalFiles }) => {
+ await personalFiles.navigate();
+ await checkFolderInformation(personalFiles, folderXFiles, 'for 3 files', `/Company Home/User Homes/${username}`);
+ });
+
+ test('[XAT-17753] Folder information correct folder size and number of documents - folder and files', async ({ personalFiles }) => {
+ await personalFiles.navigate();
+ await checkFolderInformation(personalFiles, folderXFilesAndFolders, 'for 5 files', `/Company Home/User Homes/${username}`);
+ });
+
+ test('[XAT-17758] Folder information correct folder size and number of documents - from libraries', async ({ myLibrariesPage }) => {
+ await myLibrariesPage.navigate();
+ await myLibrariesPage.dataTable.getRowByName(libraryForFolder).dblclick();
+ await checkFolderInformation(myLibrariesPage, folderInLibrary, 'for 1 files', `/Company Home/Sites/${libraryForFolder}/documentLibrary`);
+ });
+
+ test('[XAT-17759] Folder information correct folder size and number of documents - from search', async ({ personalFiles, searchPage }) => {
+ await personalFiles.navigate();
+ await searchPage.searchWithin(folderForSearch, 'folders');
+ await checkFolderInformation(searchPage, folderForSearch, 'for 2 files', `/Company Home/User Homes/${username}`);
+ });
+
+ test('[XAT-17766] Folder information correct folder size and number of documents - nested folders', async ({ personalFiles }) => {
+ await personalFiles.navigate();
+ await personalFiles.dataTable.getRowByName(folderNested1).dblclick();
+ await personalFiles.dataTable.getRowByName(folderNested2).dblclick();
+ await checkFolderInformation(
+ personalFiles,
+ folderNested3,
+ 'for 1 files',
+ `/Company Home/User Homes/${username}/${folderNested1}/${folderNested2}`
+ );
+ });
+});
diff --git a/e2e/playwright/folder-information-actions/tsconfig.e2e.adf.json b/e2e/playwright/folder-information-actions/tsconfig.e2e.adf.json
new file mode 100644
index 000000000..47ea883b4
--- /dev/null
+++ b/e2e/playwright/folder-information-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/folder-information-actions/tsconfig.e2e.json b/e2e/playwright/folder-information-actions/tsconfig.e2e.json
new file mode 100755
index 000000000..16d87d8ec
--- /dev/null
+++ b/e2e/playwright/folder-information-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"]
+}
diff --git a/projects/aca-playwright-shared/src/page-objects/components/dialogs/folder-information-dialog.component.ts b/projects/aca-playwright-shared/src/page-objects/components/dialogs/folder-information-dialog.component.ts
new file mode 100644
index 000000000..d945a7afa
--- /dev/null
+++ b/projects/aca-playwright-shared/src/page-objects/components/dialogs/folder-information-dialog.component.ts
@@ -0,0 +1,49 @@
+/*!
+ * 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 { Page } from '@playwright/test';
+import { BaseComponent } from '../base.component';
+
+export class FolderInformationDialogComponent extends BaseComponent {
+ private static readonly rootElement = '[data-automation-id="adf-dialog-container"]';
+
+ constructor(page: Page) {
+ super(page, FolderInformationDialogComponent.rootElement);
+ }
+
+ folderName = this.getChild('.aca-folder-info-header');
+ doneButton = this.getChild('[data-automation-id="adf-dialog-actions-confirm"]');
+ folderSize = this.getChild('[data-automation-id="folder-info-size"]');
+ folderLocation = this.getChild('[data-automation-id="folder-info-location"]');
+ folderCreationDate = this.getChild('[data-automation-id="folder-info-creation-date"]');
+ folderModifiedDate = this.getChild('[data-automation-id="folder-info-modify-date"]');
+
+ async getFolderSizeNumber(): Promise {
+ const textContent = await this.folderSize.textContent();
+ if (!textContent) {
+ throw new Error('Folder size text content is null or undefined');
+ }
+ return parseInt(textContent.split(' ')[0].replace(/,/g, ''), 10);
+ }
+}
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 f421cac60..10e50bae9 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
@@ -36,4 +36,4 @@ export * from './upload-dialog.component';
export * from './delete-trash-dialog.component';
export * from './link-rules.component';
export * from './edit-dialog.component';
-export * from './manage-versions-dialog.component';
+export * from './folder-information-dialog.component';
diff --git a/projects/aca-playwright-shared/src/page-objects/pages/my-libraries.page.ts b/projects/aca-playwright-shared/src/page-objects/pages/my-libraries.page.ts
index 9eb8a3b9f..d8b9dbe9e 100644
--- a/projects/aca-playwright-shared/src/page-objects/pages/my-libraries.page.ts
+++ b/projects/aca-playwright-shared/src/page-objects/pages/my-libraries.page.ts
@@ -36,7 +36,8 @@ import {
Breadcrumb,
SidenavComponent,
CreateFromTemplateDialogComponent,
- AdfConfirmDialogComponent
+ AdfConfirmDialogComponent,
+ FolderInformationDialogComponent
} from '../components';
export class MyLibrariesPage extends BasePage {
@@ -58,6 +59,7 @@ export class MyLibrariesPage extends BasePage {
public contentNodeSelector = new ContentNodeSelectorDialog(this.page);
public createFromTemplateDialogComponent = new CreateFromTemplateDialogComponent(this.page);
public confirmDialogComponent = new AdfConfirmDialogComponent(this.page);
+ public folderInformationDialog = new FolderInformationDialogComponent(this.page);
async selectCreateLibrary(): Promise {
await this.acaHeader.createButton.click();
diff --git a/projects/aca-playwright-shared/src/page-objects/pages/personal-files.page.ts b/projects/aca-playwright-shared/src/page-objects/pages/personal-files.page.ts
index 39de00d74..71959eb6c 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
@@ -45,7 +45,8 @@ import {
ManageVersionsDialog,
UploadDialog,
SnackBarComponent,
- EditDialog
+ EditDialog,
+ FolderInformationDialogComponent
} from '../components';
export class PersonalFilesPage extends BasePage {
@@ -76,6 +77,7 @@ export class PersonalFilesPage extends BasePage {
public uploadDialog = new UploadDialog(this.page);
public snackBar = new SnackBarComponent(this.page);
public editDialog = new EditDialog(this.page);
+ public folderInformationDialog = new FolderInformationDialogComponent(this.page);
async selectCreateFolder(): Promise {
await this.acaHeader.createButton.click();
diff --git a/projects/aca-playwright-shared/src/page-objects/pages/search.page.ts b/projects/aca-playwright-shared/src/page-objects/pages/search.page.ts
index 08bad1966..93f935ec9 100644
--- a/projects/aca-playwright-shared/src/page-objects/pages/search.page.ts
+++ b/projects/aca-playwright-shared/src/page-objects/pages/search.page.ts
@@ -38,7 +38,8 @@ import {
SearchFiltersDate,
SearchFiltersLocation,
SearchFiltersLogic,
- SearchFiltersProperties
+ SearchFiltersProperties,
+ FolderInformationDialogComponent
} from '../components';
import { AcaHeader } from '../components/aca-header.component';
import { AdfConfirmDialogComponent, AdfFolderDialogComponent, UploadNewVersionDialog, ManageVersionsDialog } from '../components/dialogs';
@@ -71,6 +72,7 @@ export class SearchPage extends BasePage {
public confirmDialogComponent = new AdfConfirmDialogComponent(this.page);
public uploadNewVersionDialog = new UploadNewVersionDialog(this.page);
public manageVersionsDialog = new ManageVersionsDialog(this.page);
+ public folderInformationDialog = new FolderInformationDialogComponent(this.page);
async searchWithin(searchText: string, searchType?: SearchType): Promise {
await this.acaHeader.searchButton.click();