diff --git a/e2e/playwright/folder-rules/src/tests/transform-rules.e2e.ts b/e2e/playwright/folder-rules/src/tests/transform-rules.e2e.ts
new file mode 100644
index 000000000..2c38dc1d3
--- /dev/null
+++ b/e2e/playwright/folder-rules/src/tests/transform-rules.e2e.ts
@@ -0,0 +1,105 @@
+/*!
+ * 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 {
+ ActionType,
+ ApiClientFactory,
+ test,
+ Utils,
+ TrashcanApi,
+ NodesApi,
+ MimeType,
+ FileActionsApi,
+ TEST_FILES
+} from '@alfresco/aca-playwright-shared';
+
+test.use({ launchOptions: { slowMo: 300 } });
+test.describe('Folder Rules Actions', () => {
+ const apiClientFactory = new ApiClientFactory();
+ let nodesApi: NodesApi;
+ let trashcanApi: TrashcanApi;
+ let fileActionApi: FileActionsApi;
+ const username = `user-e2e-${Utils.random()}`;
+
+ const randomFolderName1 = `folder-name-${Utils.random()}`;
+ const randomDocxName = `${TEST_FILES.DOCX.name}-${Utils.random()}`;
+ const randomXLSXName = `${TEST_FILES.XLSX.name}-${Utils.random()}`;
+ const randomPPTXName = `${TEST_FILES.PPTX_FILE.name}-${Utils.random()}`;
+
+ const copyFileName = `copy-file-${Utils.random()}`;
+ const testString = '"!@£$%^&*()_+{}|:""?><,/.\';][=-`~"';
+
+ let randomFolderName1Id: string;
+
+ test.beforeAll(async () => {
+ try {
+ await apiClientFactory.setUpAcaBackend('admin');
+ await apiClientFactory.createUser({ username });
+ nodesApi = await NodesApi.initialize(username, username);
+ trashcanApi = await TrashcanApi.initialize(username, username);
+ fileActionApi = await FileActionsApi.initialize(username, username);
+ randomFolderName1Id = (await nodesApi.createFolder(randomFolderName1)).entry.id;
+ await nodesApi.createFile(copyFileName, randomFolderName1Id);
+ } catch (error) {
+ console.error(`beforeAll failed : ${error}`);
+ }
+ });
+
+ test.beforeEach(async ({ loginPage }) => {
+ await Utils.tryLoginUser(loginPage, username, username, 'beforeEach failed');
+ });
+
+ test.afterAll(async () => {
+ await Utils.deleteNodesSitesEmptyTrashcan(nodesApi, trashcanApi, 'afterAll failed');
+ });
+
+ test('[XAT-8050] Supported types transformation to PDF', async ({ personalFiles, nodesPage, loginPage }) => {
+ const toFolderName = 'TO_PDF';
+ await nodesApi.createFolder(toFolderName);
+ await personalFiles.navigate({ remoteUrl: `#/nodes/${randomFolderName1Id}/rules` });
+ await nodesPage.toolbar.clickCreateRuleButton();
+ await nodesPage.manageRulesDialog.ruleNameInputLocator.fill(testString);
+ await nodesPage.manageRulesDialog.ruleDescriptionInputLocator.fill(testString);
+ await nodesPage.actionsDropdown.selectAction(ActionType.TransformAndCopyContent, 0);
+ await nodesPage.actionsDropdown.selectMimeType(MimeType.AdobePDFDocument, 0);
+ await nodesPage.actionsDropdown.selectDestinationFolderTransformAndCopyContent(0, toFolderName);
+ await nodesPage.manageRulesDialog.createRuleButton.click();
+ await expect(nodesPage.manageRules.getGroupsList(testString)).toBeVisible();
+ await fileActionApi.uploadFile(TEST_FILES.DOCX.path, randomDocxName, randomFolderName1Id);
+ await fileActionApi.uploadFile(TEST_FILES.XLSX.path, randomXLSXName, randomFolderName1Id);
+ await fileActionApi.uploadFile(TEST_FILES.PPTX_FILE.path, randomPPTXName, randomFolderName1Id);
+ await loginPage.logoutUser();
+ await expect(loginPage.username, 'User name was not visible').toBeVisible();
+ await Utils.tryLoginUser(loginPage, username, username, 'beforeEach failed');
+ await personalFiles.dataTable.performClickFolderOrFileToOpen(toFolderName);
+ await personalFiles.spinner.waitForReload();
+ const docxToPDF = `${randomDocxName}.pdf`;
+ const xlsxToPDF = `${randomXLSXName}.pdf`;
+ const pptxToPDF = `${randomPPTXName}.pdf`;
+ expect(await personalFiles.dataTable.isItemPresent(docxToPDF), `Converted PDF from DOCX ${docxToPDF} was not present in data table`).toBe(true);
+ expect(await personalFiles.dataTable.isItemPresent(pptxToPDF), `Converted PDF from PPTX ${pptxToPDF} was not present in data table`).toBe(true);
+ expect(await personalFiles.dataTable.isItemPresent(xlsxToPDF), `Converted PDF from XLSX ${xlsxToPDF} was not present in data table`).toBe(true);
+ });
+});
diff --git a/projects/aca-playwright-shared/src/page-objects/components/actions-dropdown.component.ts b/projects/aca-playwright-shared/src/page-objects/components/actions-dropdown.component.ts
index 02769d5f8..5f902f660 100644
--- a/projects/aca-playwright-shared/src/page-objects/components/actions-dropdown.component.ts
+++ b/projects/aca-playwright-shared/src/page-objects/components/actions-dropdown.component.ts
@@ -24,6 +24,7 @@
import { Locator, Page } from '@playwright/test';
import { BaseComponent } from './base.component';
+import { timeouts } from '../../public-api';
export enum ActionType {
AddAspect = 'Add aspect',
@@ -49,6 +50,10 @@ export enum ActionType {
TransformAndCopyImage = 'Transform and copy image'
}
+export enum MimeType {
+ AdobePDFDocument = 'Adobe PDF Document [application/pdf]'
+}
+
export class ActionsDropdownComponent extends BaseComponent {
private static rootElement = 'aca-edit-rule-dialog aca-rule-action-list';
@@ -67,6 +72,9 @@ export class ActionsDropdownComponent extends BaseComponent {
private actionSimpleWorkflowLabelApproveLocator = `[data-automation-id="card-boolean-label-approve-move"]`;
private actionSimpleWorkflowSRejectStepLocator = '[data-automation-id="header-reject-step"] input';
private actionSimpleWorkflowRejectFolderLocator = `[data-automation-id="header-reject-folder"] input`;
+ private readonly mimeTypeDropdownLocator = this.getChild('[data-automation-class="select-box"][aria-label="Mimetype *"]');
+ private readonly actionTransformAndCopyContentDestinationFolderLocator = '[data-automation-id="card-textitem-value-destination-folder"]';
+ private readonly contentNodeSelectorSearchInput = '[data-automation-id="content-node-selector-search-input"]';
constructor(page: Page) {
super(page, ActionsDropdownComponent.rootElement);
@@ -112,4 +120,62 @@ export class ActionsDropdownComponent extends BaseComponent {
await this.ruleActionLocator.nth(index).locator(this.actionSimpleWorkflowRejectFolderLocator).click();
await this.page.locator(this.actionSimpleWorkflowActionChoiceLocator).click();
}
+
+ /**
+ * Selects a MIME type from the dropdown for content transformation actions.
+ * This method is typically used when configuring folder rules with transform actions
+ * (e.g., Transform and Copy Content) to specify the target format for file conversion.
+ *
+ * @param mimeType - The MIME type to select (e.g., MimeType.AdobePDFDocument for PDF conversion)
+ * @param index - The zero-based index of the action dropdown (use 0 for the first action, 1 for the second, etc.)
+ *
+ * @example
+ * ```typescript
+ * // Select PDF as the target format for the first transform action
+ * await actionsDropdown.selectMimeType(MimeType.AdobePDFDocument, 0);
+ * ```
+ */
+ async selectMimeType(mimeType: Partial, index: number): Promise {
+ await this.mimeTypeDropdownLocator.nth(index).hover({ timeout: timeouts.short });
+ await this.mimeTypeDropdownLocator.nth(index).click();
+ const option = this.getOptionLocator(mimeType);
+ await option.click();
+ }
+
+ /**
+ * Selects a destination folder for the Transform and Copy Content action.
+ * This method opens the folder picker dialog, searches for a folder by name,
+ * and selects it as the destination where transformed files will be copied.
+ *
+ * The method performs the following steps:
+ * 1. Clicks the destination folder field to open the content node selector dialog
+ * 2. Fills the search input with the provided search term
+ * 3. Waits for search results to load
+ * 4. Clicks on the first matching folder from the results
+ * 5. Confirms the selection by clicking the Choose button
+ *
+ * @param index - The zero-based index of the action dropdown (use 0 for the first action, 1 for the second, etc.)
+ * @param searchTerm - The name or partial name of the destination folder to search for (e.g., 'TO_PDF')
+ *
+ * @example
+ * ```typescript
+ * // Select 'TO_PDF' folder as the destination for transformed files
+ * await actionsDropdown.selectDestinationFolderTransformAndCopyContent(0, 'TO_PDF');
+ * ```
+ */
+ async selectDestinationFolderTransformAndCopyContent(index: number, searchTerm: string): Promise {
+ // Click on the destination folder input field to open the folder picker dialog
+ await this.ruleActionLocator.nth(index).locator(this.actionTransformAndCopyContentDestinationFolderLocator).click();
+
+ // Locate and fill the search input field within the content node selector
+ const searchInput = this.page.locator(this.contentNodeSelectorSearchInput);
+ await searchInput.fill(searchTerm);
+
+ // Click on the folder from the search results (first match if multiple)
+ const folderRow = this.page.locator('.adf-datatable-body .adf-name-location-cell-name', { hasText: searchTerm }).first();
+ await folderRow.click();
+
+ // Click the Choose button to confirm selection
+ await this.page.locator(this.actionSimpleWorkflowActionChoiceLocator).click();
+ }
}