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(); + } }