From 9bd0ce770b8ec63b69f453bc6ffd4083cd92e7b9 Mon Sep 17 00:00:00 2001 From: Bryan Date: Tue, 17 Dec 2024 21:43:30 -0600 Subject: [PATCH] Issue 30682 automation phase1 (#30946) ### Proposed Changes * Adding content editing test ### Checklist - [x] Tests --- .../frontend/locators/globalLocators.ts | 5 ++ .../tests/contentSearch/contentData.ts | 8 ++ .../contentSearch/contentEditing.spec.ts | 51 +++++++++++- .../contentSearch/portletIntegrity.spec.ts | 1 + .../frontend/utils/contentUtils.ts | 81 ++++++++++++++----- .../frontend/utils/dotCMSUtils.ts | 19 ++++- 6 files changed, 137 insertions(+), 28 deletions(-) diff --git a/e2e/dotcms-e2e-node/frontend/locators/globalLocators.ts b/e2e/dotcms-e2e-node/frontend/locators/globalLocators.ts index e613d4d2e61..f1a99a2e195 100644 --- a/e2e/dotcms-e2e-node/frontend/locators/globalLocators.ts +++ b/e2e/dotcms-e2e-node/frontend/locators/globalLocators.ts @@ -33,6 +33,11 @@ export const contentGeneric = { label: "Content (Generic)" } +export const fileAsset = { + locator: "attach_fileFile Asset", + label: "File Asset" +} + export { } from './navigation/menuLocators'; diff --git a/e2e/dotcms-e2e-node/frontend/tests/contentSearch/contentData.ts b/e2e/dotcms-e2e-node/frontend/tests/contentSearch/contentData.ts index e7a44c8b7db..57aa0c7d21e 100644 --- a/e2e/dotcms-e2e-node/frontend/tests/contentSearch/contentData.ts +++ b/e2e/dotcms-e2e-node/frontend/tests/contentSearch/contentData.ts @@ -20,4 +20,12 @@ export const contentProperties = { deleteWfAction: "Delete" } +export const fileAssetContent = { + title: "File Asset title", + body: "This is a sample file asset content", + fromURL:"https://upload.wikimedia.org/wikipedia/commons/0/03/DotCMS-logo.svg", + newFileName:"New file asset", + newFileText:"This is a new file asset content", + host:"default" +} diff --git a/e2e/dotcms-e2e-node/frontend/tests/contentSearch/contentEditing.spec.ts b/e2e/dotcms-e2e-node/frontend/tests/contentSearch/contentEditing.spec.ts index e98b53a8e79..4d52ecd95d1 100644 --- a/e2e/dotcms-e2e-node/frontend/tests/contentSearch/contentEditing.spec.ts +++ b/e2e/dotcms-e2e-node/frontend/tests/contentSearch/contentEditing.spec.ts @@ -1,4 +1,4 @@ -import {expect, test} from '@playwright/test'; +import {expect, Page, test} from '@playwright/test'; import {dotCMSUtils, waitForVisibleAndCallback} from '../../utils/dotCMSUtils'; import { GroupEntriesLocators, @@ -6,8 +6,8 @@ import { ToolEntriesLocators } from '../../locators/navigation/menuLocators'; import {ContentUtils} from "../../utils/contentUtils"; -import {iFramesLocators, contentGeneric} from "../../locators/globalLocators"; -import {genericContent1, contentProperties} from "./contentData"; +import {iFramesLocators, contentGeneric, fileAsset} from "../../locators/globalLocators"; +import {genericContent1, contentProperties, fileAssetContent} from "./contentData"; import {assert} from "console"; const cmsUtils = new dotCMSUtils(); @@ -35,7 +35,9 @@ test.beforeEach('Navigate to content portlet', async ({page}) => { await waitForVisibleAndCallback(breadcrumbLocator, () => expect(breadcrumbLocator).toContainText('Search All')); }); - +/** + * test to add a new piece of content (generic content) + */ test('Add a new pice of content', async ({page}) => { const contentUtils = new ContentUtils(page); const iframe = page.frameLocator(iFramesLocators.main_iframe); @@ -43,6 +45,8 @@ test('Add a new pice of content', async ({page}) => { // Adding new rich text content await contentUtils.addNewContentAction(page, contentGeneric.locator, contentGeneric.label); await contentUtils.fillRichTextForm(page, genericContent1.title, genericContent1.body, contentProperties.publishWfAction); + await contentUtils.workflowExecutionValidationAndClose(page, 'Content saved'); + await waitForVisibleAndCallback(iframe.locator('#results_table tbody tr').first(), async () => {}); await contentUtils.validateContentExist(page, genericContent1.title).then(assert); @@ -73,5 +77,44 @@ test('Delete a piece of content', async ({ page }) => { } ); +/** + * Test to make sure we are validating the required of text fields on the content creation + * */ +test('Validate required on text fields', async ({page}) => { + const contentUtils = new ContentUtils(page); + const iframe = page.frameLocator(iFramesLocators.main_iframe).first(); + + await contentUtils.addNewContentAction(page, contentGeneric.locator, contentGeneric.label); + await contentUtils.fillRichTextForm(page, '', genericContent1.body, contentProperties.publishWfAction); + await expect(iframe.getByText('Error x')).toBeVisible(); + await expect(iframe.getByText('The field Title is required.')).toBeVisible(); +}); + +/** Please enable after fixing the issue #30748 + * Test to make sure we are validating the required of blockEditor fields on the content creation + */ +/** +test('Validate required on blockContent fields', async ({page}) => { + const contentUtils = new ContentUtils(page); + const iframe = page.frameLocator(iFramesLocators.main_iframe).first(); + + await contentUtils.addNewContentAction(page, contentGeneric.locator, contentGeneric.label); + await contentUtils.fillRichTextForm(page, genericContent1.title, '', contentProperties.publishWfAction); + await expect(iframe.getByText('Error x')).toBeVisible(); + await expect(iframe.getByText('The field Title is required.')).toBeVisible(); +}); +*/ +/** + * Test to validate you are able to add file assets importing from url + */ +test('Validate you are able to add file assets importing from url', async ({page}) => { + const contentUtils = new ContentUtils(page); + const iframe = page.frameLocator(iFramesLocators.main_iframe); + await contentUtils.addNewContentAction(page, fileAsset.locator, fileAsset.label); + await contentUtils.fillFileAssetForm(page, fileAssetContent.host, fileAssetContent.title, contentProperties.publishWfAction, null, fileAssetContent.fromURL ); + //fileName?: string, fromURL?: string, newFileName?: string, newFileText?: string) { + await contentUtils.workflowExecutionValidationAndClose(page, 'Content saved'); + await contentUtils.validateContentExist(page, fileAssetContent.title).then(assert); +}); \ No newline at end of file diff --git a/e2e/dotcms-e2e-node/frontend/tests/contentSearch/portletIntegrity.spec.ts b/e2e/dotcms-e2e-node/frontend/tests/contentSearch/portletIntegrity.spec.ts index 876ec9b7cf0..5b9a5572285 100644 --- a/e2e/dotcms-e2e-node/frontend/tests/contentSearch/portletIntegrity.spec.ts +++ b/e2e/dotcms-e2e-node/frontend/tests/contentSearch/portletIntegrity.spec.ts @@ -55,6 +55,7 @@ test('Search filter', async ({page}) => { // Adding new rich text content await contentUtils.addNewContentAction(page, contentGeneric.locator, contentGeneric.label); await contentUtils.fillRichTextForm(page, genericContent1.title, genericContent1.body, contentProperties.publishWfAction); + await contentUtils.workflowExecutionValidationAndClose(page, 'Content saved'); // Validate the content has been created await expect.soft(iframe.getByRole('link', {name: genericContent1.title}).first()).toBeVisible(); diff --git a/e2e/dotcms-e2e-node/frontend/utils/contentUtils.ts b/e2e/dotcms-e2e-node/frontend/utils/contentUtils.ts index 267f9e4ad40..a12eff7db9b 100644 --- a/e2e/dotcms-e2e-node/frontend/utils/contentUtils.ts +++ b/e2e/dotcms-e2e-node/frontend/utils/contentUtils.ts @@ -1,5 +1,5 @@ import {expect, FrameLocator, Locator, Page} from '@playwright/test'; -import {contentGeneric, iFramesLocators} from '../locators/globalLocators'; +import {contentGeneric, iFramesLocators, fileAsset } from '../locators/globalLocators'; import {waitForVisibleAndCallback} from './dotCMSUtils'; import {contentProperties} from "../tests/contentSearch/contentData"; @@ -27,14 +27,53 @@ export class ContentUtils { await dotIframe.locator('#title').fill(title); //Fill body await dotIframe.locator('#block-editor-body div').nth(1).fill(body); - - //await dotIframe.locator(iFramesLocators.wysiwygFrame).contentFrame().locator('#tinymce').fill(body); //Click on action await dotIframe.getByText(action).first().click(); - //Wait for the content to be saved + } + + /** + * Fill the file asset form + * @param page + * @param host + * @param title + * @param action + * @param fileName + * @param fromURL + * @param newFileName + * @param newFileText + */ + async fillFileAssetForm(page: Page, host: string, title: string, action:string, fileName?: string, fromURL?: string, newFileName?: string, newFileText?: string) { + const dotIframe = page.frameLocator(iFramesLocators.dot_iframe); + + const headingLocator = page.getByRole('heading'); + await waitForVisibleAndCallback(headingLocator, () => expect.soft(headingLocator).toContainText( fileAsset.label )); + + await dotIframe.locator('#HostSelector-hostFolderSelect').fill(host); + if (newFileName && newFileText) { + await dotIframe.getByTestId('editor-file-name').fill(newFileName); + await dotIframe.getByLabel('Editor content;Press Alt+F1').fill(newFileText); + await dotIframe.getByRole('button', { name: 'Save' }).click(); + } else { + if (fromURL) { + await dotIframe.getByRole('button', {name: ' Import from URL'}).click(); + await dotIframe.getByTestId('url-input').fill(fromURL); + await dotIframe.getByRole('button', { name: ' Import' }).click(); + } + } + const titleField = dotIframe.locator('#title'); + await waitForVisibleAndCallback(headingLocator, () => titleField.fill(title)); + await dotIframe.getByText(action).first().click(); + } - await expect(dotIframe.getByText('Content saved')).toBeVisible({timeout: 9000}); - await expect(dotIframe.getByText('Content saved')).toBeHidden(); + /** + * Validate the workflow execution and close the modal + * @param page + */ + async workflowExecutionValidationAndClose(page: Page, message: string) { + const dotIframe = page.frameLocator(iFramesLocators.dot_iframe); + + await expect(dotIframe.getByText(message)).toBeVisible({timeout: 9000}); + await expect(dotIframe.getByText(message)).toBeHidden(); //Click on close const closeBtnLocator = page.getByTestId('close-button').getByRole('button'); await waitForVisibleAndCallback(closeBtnLocator, () => closeBtnLocator.click()); @@ -48,16 +87,10 @@ export class ContentUtils { */ async addNewContentAction(page: Page, typeLocator: string, typeString: string) { const iframe = page.frameLocator(iFramesLocators.main_iframe); + const structureINodeLocator = iframe.locator('#structure_inode'); await waitForVisibleAndCallback(structureINodeLocator, () => expect(structureINodeLocator).toBeVisible()); - //TODO remove this - await page.waitForTimeout(1000); - const structureINodeDivLocator = iframe.locator('#widget_structure_inode div').first(); - await waitForVisibleAndCallback(structureINodeDivLocator, () => structureINodeDivLocator.click()); - //TODO remove this - await page.waitForTimeout(1000); - const typeLocatorByTextLocator = iframe.getByText(typeLocator); - await waitForVisibleAndCallback(typeLocatorByTextLocator, () => typeLocatorByTextLocator.click()); + await this.selectTypeOnFilter(page, typeLocator); await iframe.locator('#dijit_form_DropDownButton_0').click(); await expect(iframe.getByLabel('actionPrimaryMenu')).toBeVisible(); @@ -72,12 +105,13 @@ export class ContentUtils { * @param typeLocator * @param typeString */ - async selectTypeOnFilter(page: Page, typeLocator: string, typeString: string) { + async selectTypeOnFilter(page: Page, typeLocator: string) { const iframe = page.frameLocator(iFramesLocators.main_iframe); - await expect.soft(iframe.locator('#structure_inode')).toBeVisible(); - await iframe.locator('#widget_structure_inode div').first().click(); - await iframe.getByText(typeLocator).click(); + const structureINodeDivLocator = iframe.locator('#widget_structure_inode div').first(); + await waitForVisibleAndCallback(structureINodeDivLocator, () => structureINodeDivLocator.click()); + const typeLocatorByTextLocator = iframe.getByText(typeLocator); + await waitForVisibleAndCallback(typeLocatorByTextLocator, () => typeLocatorByTextLocator.click()); } /** @@ -151,6 +185,7 @@ export class ContentUtils { return; } await this.fillRichTextForm(page, newTitle, newBody, action); + await this.workflowExecutionValidationAndClose(page, 'Content saved'); } /** @@ -172,7 +207,6 @@ export class ContentUtils { await iframe.locator('#widget_showingSelect div').first().click(); const dropDownMenu = iframe.getByRole('option', { name: 'Archived' }); await waitForVisibleAndCallback(dropDownMenu, () => dropDownMenu.click()); - await page.waitForTimeout(1000) } else if (contentState === 'archived') { await this.performWorkflowAction(page, title, contentProperties.deleteWfAction); return; @@ -198,10 +232,15 @@ export class ContentUtils { } const actionBtnLocator = iframe.getByRole('menuitem', { name: action }); await waitForVisibleAndCallback(actionBtnLocator, () => actionBtnLocator.getByText(action).click()); - await expect.soft(iframe.getByText('Workflow executed')).toBeVisible(); - await expect.soft(iframe.getByText('Workflow executed')).toBeHidden(); + const executionConfirmation = iframe.getByText('Workflow executed'); + await waitForVisibleAndCallback(executionConfirmation, () => expect(executionConfirmation).toBeVisible()); + await waitForVisibleAndCallback(executionConfirmation, () => expect(executionConfirmation).toBeHidden()); } + /** + * Get the content state from the results table on the content portle + * @param page + */ async getContentState(page: Page, title: string): Promise { const iframe = page.frameLocator(iFramesLocators.main_iframe); await iframe.locator('#results_table tbody tr').first().waitFor({ state: 'visible' }); diff --git a/e2e/dotcms-e2e-node/frontend/utils/dotCMSUtils.ts b/e2e/dotcms-e2e-node/frontend/utils/dotCMSUtils.ts index d84c1bdb69d..cc47cc1aa72 100644 --- a/e2e/dotcms-e2e-node/frontend/utils/dotCMSUtils.ts +++ b/e2e/dotcms-e2e-node/frontend/utils/dotCMSUtils.ts @@ -7,8 +7,8 @@ export class dotCMSUtils { /** * Login to dotCMS * @param page - * @param username - * @param password + * @param username + * @param password */ async login(page: Page, username: string, password: string) { await page.goto('/dotAdmin'); @@ -34,16 +34,29 @@ export class dotCMSUtils { } }; - +/** + * Wait for the locator to be in the provided state + * @param locator + * @param state + */ export const waitFor = async (locator: Locator, state: "attached" | "detached" | "visible" | "hidden"): Promise => { await locator.waitFor({state: state}); } +/** + * Wait for the locator to be visible + * @param locator + */ export const waitForAndCallback = async (locator: Locator, state: "attached" | "detached" | "visible" | "hidden", callback: () => Promise): Promise => { await waitFor(locator, state); await callback(); }; +/** + * Wait for the locator to be visible and execute the callback + * @param locator + * @param callback + */ export const waitForVisibleAndCallback = async (locator: Locator, callback: () => Promise): Promise => { await waitForAndCallback(locator, 'visible', callback); }; \ No newline at end of file