From 09a35470137c625d4248b0657456e2e02ac064d8 Mon Sep 17 00:00:00 2001 From: Bryan Date: Tue, 7 Jan 2025 15:07:59 -0600 Subject: [PATCH 1/2] Issue 30781 Content Search (#31071) ### Proposed Changes * Test Refactor to new test on content search * New Tests ### Checklist - [ ] Tests --- .../frontend/locators/globalLocators.ts | 50 +- .../locators/navigation/menuLocators.ts | 61 +- .../tests/contentSearch/contentData.ts | 61 +- .../contentSearch/contentEditing.spec.ts | 592 +++++++------- .../frontend/tests/login/login.spec.ts | 60 +- .../frontend/utils/contentUtils.ts | 735 ++++++++---------- .../frontend/utils/dotCMSUtils.ts | 101 +-- 7 files changed, 748 insertions(+), 912 deletions(-) diff --git a/e2e/dotcms-e2e-node/frontend/locators/globalLocators.ts b/e2e/dotcms-e2e-node/frontend/locators/globalLocators.ts index d9e4a7aca4c0..93d077396f00 100644 --- a/e2e/dotcms-e2e-node/frontend/locators/globalLocators.ts +++ b/e2e/dotcms-e2e-node/frontend/locators/globalLocators.ts @@ -2,47 +2,49 @@ * Locators for the iframes in the main page. */ export const iFramesLocators = { - main_iframe: 'iframe[name="detailFrame"]', - dot_iframe: 'dot-iframe-dialog iframe[name="detailFrame"]', - wysiwygFrame: - 'iframe[title="Rich Text Area\\. Press ALT-F9 for menu\\. Press ALT-F10 for toolbar\\. Press ALT-0 for help"]', - dataTestId: '[data-testid="iframe"]', -}; + main_iframe: 'iframe[name="detailFrame"]', + dot_iframe: 'dot-iframe-dialog iframe[name="detailFrame"]', + wysiwygFrame: 'iframe[title="Rich Text Area\\. Press ALT-F9 for menu\\. Press ALT-F10 for toolbar\\. Press ALT-0 for help"]', + dataTestId: '[data-testid="iframe"]', + dot_edit_iframe: 'dot-edit-contentlet iframe[name="detailFrame"]', +} /** * Locators for the login functionality. */ export const loginLocators = { - userNameInput: 'input[id="inputtext"]', - passwordInput: 'input[id="password"]', - loginBtn: "submitButton", -}; + userNameInput: 'input[id="inputtext"]', + passwordInput: 'input[id="password"]', + loginBtn: 'submitButton' +} /** * Locators for the Add Content functionality. */ export const addContent = { - addBtn: "#dijit_form_DropDownButton_0", - addNewContentSubMenu: "Add New Content", - addNewMenuLabel: "▼", -}; + addBtn: '#dijit_form_DropDownButton_0', + addNewContentSubMenu: 'Add New Content', + addNewMenuLabel: '▼' +} /** * Locators for the Rich Text functionality. */ export const contentGeneric = { - locator: "articleContent (Generic)", - label: "Content (Generic)", -}; + locator: "articleContent (Generic)", + label: "Content (Generic)" +} export const fileAsset = { - locator: "attach_fileFile Asset", - label: "File Asset", -}; + locator: "attach_fileFile Asset", + label: "File Asset" +} export const pageAsset = { - locator: "descriptionPage", - label: "Page", -}; + locator: "descriptionPage", + label: "Page" +} + +export { +} from './navigation/menuLocators'; -export {} from "./navigation/menuLocators"; diff --git a/e2e/dotcms-e2e-node/frontend/locators/navigation/menuLocators.ts b/e2e/dotcms-e2e-node/frontend/locators/navigation/menuLocators.ts index 067b4b5ae472..cda521ff3009 100644 --- a/e2e/dotcms-e2e-node/frontend/locators/navigation/menuLocators.ts +++ b/e2e/dotcms-e2e-node/frontend/locators/navigation/menuLocators.ts @@ -1,49 +1,44 @@ -import { Page, Locator } from "@playwright/test"; +import {Page, Locator} from '@playwright/test'; export class GroupEntriesLocators { - readonly SITE: Locator; - readonly CONTENT: Locator; - readonly SCHEMA: Locator; - - constructor(page: Page) { - this.SITE = page.getByText("Site", { exact: true }); - this.CONTENT = page - .getByRole("complementary") - .getByText("Content", { exact: true }); - this.SCHEMA = page.getByText("Schema"); - } + readonly SITE: Locator; + readonly CONTENT: Locator; + readonly SCHEMA: Locator; + + constructor(page: Page) { + this.SITE = page.getByText('Site', {exact: true}); + this.CONTENT = page.getByRole('complementary').getByText('Content', {exact: true}); + this.SCHEMA = page.getByText('Schema'); + + } } /** * Locators for the tools in the menu */ export class ToolEntriesLocators { - readonly SEARCH_ALL: Locator; - readonly CONTENT_TYPES: Locator; - readonly CATEGORIES: Locator; - - constructor(page: Page) { - this.SEARCH_ALL = page.getByRole("link", { name: "Search All" }); - this.CONTENT_TYPES = page.getByRole("link", { name: "Content Types" }); - this.CATEGORIES = page.getByRole("link", { name: "Categories" }); - } + readonly SEARCH_ALL: Locator; + readonly CONTENT_TYPES: Locator; + readonly CATEGORIES: Locator; + + + constructor(page: Page) { + this.SEARCH_ALL = page.getByRole('link', {name: 'Search All'}); + this.CONTENT_TYPES = page.getByRole('link', {name: 'Content Types'}); + this.CATEGORIES = page.getByRole('link', { name: 'Categories' }); + } } /** * Locators for the menu entries */ export class MenuEntriesLocators { - readonly EXPAND: Locator; - readonly COLLAPSE: Locator; + readonly EXPAND: Locator; + readonly COLLAPSE: Locator; - constructor(page: Page) { - /*this.EXPAND = page.locator('button[ng-reflect-ng-class="[object Object]"]').first(); + constructor(page: Page) { + this.EXPAND = page.getByRole('button', { name: '' }); this.COLLAPSE = page.locator('button[ng-reflect-ng-class="[object Object]"]').first(); - this.EXPAND = page.locator('button[ng-reflect-ng-class="[object Object]"]'); - this.COLLAPSE = page.locator('button[ng-reflect-ng-class="[object Object]"]');*/ - this.EXPAND = page.getByRole("button", { name: "" }); - this.COLLAPSE = page - .locator('button[ng-reflect-ng-class="[object Object]"]') - .first(); - } -} + + } +} \ No newline at end of file diff --git a/e2e/dotcms-e2e-node/frontend/tests/contentSearch/contentData.ts b/e2e/dotcms-e2e-node/frontend/tests/contentSearch/contentData.ts index b298ef4c9719..5ed363b4bebe 100644 --- a/e2e/dotcms-e2e-node/frontend/tests/contentSearch/contentData.ts +++ b/e2e/dotcms-e2e-node/frontend/tests/contentSearch/contentData.ts @@ -1,41 +1,46 @@ + /** * Content to add a Rich Text content */ export const genericContent1 = { - title: "Automation Test", - body: "This is a sample content", - newTitle: "Automation Test edited", - newBody: "This is a sample content edited", -}; + title: "Automation Test", + body: "This is a sample content", + newTitle : "Automation Test edited", + newBody : "This is a sample content edited" +} /** * Content actions text content locators */ export const contentProperties = { - language: "English (US)", - publishWfAction: "Publish", - unpublishWfAction: "Unpublish", - unlockWfAction: "Unlock", - archiveWfAction: "Archive", - deleteWfAction: "Delete", -}; + language: "English (US)", + publishWfAction: "Publish", + unpublishWfAction: "Unpublish", + unlockWfAction: "Unlock", + archiveWfAction: "Archive", + 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.txt", - newFileText: "This is a new file asset content", - host: "default", -}; + 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.txt", + newFileText:"This is a new file asset content", + newFileTextEdited:"Validate you are able to edit text on binary fields", + host:"default" +} export const pageAssetContent = { - title: "PageAsset1", - host: "default", - template: "System Template", - friendlyName: "friendlyName-test", - showOnMenu: true, - sortOrder: "1", - cacheTTL: 0, -}; + title: "PageAsset1", + host: "default", + template: "System Template", + friendlyName: "friendlyName-test", + showOnMenu: true, + sortOrder: "1", + cacheTTL: 0, +} + + + + 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 c3cfaa29d97c..21cfa348ec1a 100644 --- a/e2e/dotcms-e2e-node/frontend/tests/contentSearch/contentEditing.spec.ts +++ b/e2e/dotcms-e2e-node/frontend/tests/contentSearch/contentEditing.spec.ts @@ -1,412 +1,348 @@ -import { expect, test } from "@playwright/test"; +import {expect, test} from '@playwright/test'; +import {dotCMSUtils, waitForVisibleAndCallback} from '../../utils/dotCMSUtils'; import { - dotCMSUtils, - waitForVisibleAndCallback, -} from "../../utils/dotCMSUtils"; -import { - GroupEntriesLocators, - MenuEntriesLocators, - ToolEntriesLocators, -} from "../../locators/navigation/menuLocators"; -import { ContentUtils } from "../../utils/contentUtils"; -import { - iFramesLocators, - contentGeneric, - fileAsset, - pageAsset, -} from "../../locators/globalLocators"; -import { - genericContent1, - contentProperties, - fileAssetContent, - pageAssetContent, -} from "./contentData"; -import { assert } from "console"; + GroupEntriesLocators, + MenuEntriesLocators, + ToolEntriesLocators +} from '../../locators/navigation/menuLocators'; +import {ContentUtils} from "../../utils/contentUtils"; +import {iFramesLocators, contentGeneric, fileAsset, pageAsset} from "../../locators/globalLocators"; +import {genericContent1, contentProperties, fileAssetContent, pageAssetContent} from "./contentData"; +import {assert} from "console"; + /** * Test to navigate to the content portlet and login to the dotCMS instance * @param page */ -test.beforeEach("Navigate to content portlet", async ({ page }) => { - const cmsUtils = new dotCMSUtils(); - const menuLocators = new MenuEntriesLocators(page); - const groupsLocators = new GroupEntriesLocators(page); - const toolsLocators = new ToolEntriesLocators(page); - - // Get the username and password from the environment variables - const username = process.env.USERNAME as string; - const password = process.env.PASSWORD as string; - - // Login to dotCMS - await cmsUtils.login(page, username, password); - await cmsUtils.navigate( - menuLocators.EXPAND, - groupsLocators.CONTENT, - toolsLocators.SEARCH_ALL, - ); - - // Validate the portlet title - const breadcrumbLocator = page.locator("p-breadcrumb"); - await waitForVisibleAndCallback(breadcrumbLocator, () => - expect(breadcrumbLocator).toContainText("Search All"), - ); +test.beforeEach('Navigate to content portlet', async ({page}) => { + const cmsUtils = new dotCMSUtils(); + const menuLocators = new MenuEntriesLocators(page); + const groupsLocators = new GroupEntriesLocators(page); + const toolsLocators = new ToolEntriesLocators(page); + + // Get the username and password from the environment variables + const username = process.env.USERNAME as string; + const password = process.env.PASSWORD as string; + + // Login to dotCMS + await cmsUtils.login(page, username, password); + await cmsUtils.navigate(menuLocators.EXPAND, groupsLocators.CONTENT, toolsLocators.SEARCH_ALL); + + // Validate the portlet title + const breadcrumbLocator = page.locator('p-breadcrumb'); + await waitForVisibleAndCallback(breadcrumbLocator, () => expect(breadcrumbLocator).toContainText('Search All')); }); /** * test to add a new piece of content (generic content) */ -test("Add a new Generic content", async ({ page }) => { - const contentUtils = new ContentUtils(page); - const iframe = page.frameLocator(iFramesLocators.main_iframe); - - // 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()); - - await contentUtils - .validateContentExist(page, genericContent1.title) - .then(assert); +test('Add a new Generic content', async ({page}) => { + const contentUtils = new ContentUtils(page); + const iframe = page.frameLocator(iFramesLocators.main_iframe); + + // 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); }); /** * Test to edit an existing piece of content */ -test("Edit a generic content", async ({ page }) => { - const contentUtils = new ContentUtils(page); - const iframe = page.frameLocator(iFramesLocators.main_iframe); - - // Edit the content - await contentUtils.selectTypeOnFilter(page, contentGeneric.locator); - await contentUtils.editContent( - page, - genericContent1.title, - genericContent1.newTitle, - genericContent1.newBody, - contentProperties.publishWfAction, - ); - await waitForVisibleAndCallback(iframe.locator("#results_table tbody tr").first()); - await contentUtils - .validateContentExist(page, genericContent1.newTitle) - .then(assert); +test('Edit a generic content', async ({page}) => { + const contentUtils = new ContentUtils(page); + const iframe = page.frameLocator(iFramesLocators.main_iframe); + + // Edit the content + await contentUtils.selectTypeOnFilter(page, contentGeneric.locator); + await contentUtils.editContent(page, genericContent1.title, genericContent1.newTitle, genericContent1.newBody, contentProperties.publishWfAction); + await waitForVisibleAndCallback(iframe.locator('#results_table tbody tr').first(), async () => {}); + await contentUtils.validateContentExist(page, genericContent1.newTitle).then(assert); }); + /** * Test to delete an existing piece of content */ -test("Delete a generic of content", async ({ page }) => { - const contentUtils = new ContentUtils(page); - // Delete the content - await contentUtils.deleteContent(page, genericContent1.newTitle); -}); +test('Delete a generic of content', async ({ page }) => { + const contentUtils = new ContentUtils(page); + // Delete the content + await contentUtils.deleteContent(page, genericContent1.newTitle); + } +); /** * 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.dot_iframe); - - 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(); +test('Validate required on text fields', async ({page}) => { + const contentUtils = new ContentUtils(page); + const iframe = page.frameLocator(iFramesLocators.dot_iframe); + + 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}) => { + 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).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(); + await contentUtils.addNewContentAction(page, fileAsset.locator, fileAsset.label); + const params = { + page: page, + host: fileAssetContent.host, + title: fileAssetContent.title, + editContent: true, + action: contentProperties.publishWfAction, + fromURL: fileAssetContent.fromURL + }; + await contentUtils.fillFileAssetForm(params); + await contentUtils.workflowExecutionValidationAndClose(page, 'Content saved'); + await expect(contentUtils.validateContentExist(page, 'DotCMS-logo.svg')).resolves.toBeTruthy(); }); -*/ /** - * Test to validate you are able to add file assets importing from url + * Test to validate you are able to add file assets creating a new file */ -test("Validate you are able to add file assets importing from url", async ({ - page, -}) => { - const contentUtils = new ContentUtils(page); - - await contentUtils.addNewContentAction( - page, - fileAsset.locator, - fileAsset.label, - ); - const params = { - page: page, - host: fileAssetContent.host, - title: fileAssetContent.title, - action: contentProperties.publishWfAction, - fromURL: fileAssetContent.fromURL, - }; - await contentUtils.fillFileAssetForm(params); - await contentUtils.workflowExecutionValidationAndClose(page, "Content saved"); - await expect( - contentUtils.validateContentExist(page, "DotCMS-logo.svg"), - ).resolves.toBeTruthy(); +test('Validate you are able to add file assets creating a new file', async ({page}) => { + const contentUtils = new ContentUtils(page); + + await contentUtils.addNewContentAction(page, fileAsset.locator, fileAsset.label); + const params = { + page: page, + host: fileAssetContent.host, + editContent: false, + title: fileAssetContent.title, + action: contentProperties.publishWfAction, + binaryFileName: fileAssetContent.newFileName, + binaryFileText: fileAssetContent.newFileText, + + }; + await contentUtils.fillFileAssetForm(params); + await contentUtils.workflowExecutionValidationAndClose(page, 'Content saved'); + await contentUtils.validateContentExist(page, fileAssetContent.newFileName).then(assert); }); /** - * Test to validate you are able to add file assets creating a new file + * Test to validate you are able to edit file assets text */ -test("Validate you are able to add file assets creating a new file", async ({ - page, -}) => { - const contentUtils = new ContentUtils(page); - - await contentUtils.addNewContentAction( - page, - fileAsset.locator, - fileAsset.label, - ); - const params = { - page: page, - host: fileAssetContent.host, - title: fileAssetContent.title, - action: contentProperties.publishWfAction, - newFileName: fileAssetContent.newFileName, - newFileText: fileAssetContent.newFileText, - }; - await contentUtils.fillFileAssetForm(params); - await contentUtils.workflowExecutionValidationAndClose(page, "Content saved"); - await contentUtils - .validateContentExist(page, fileAssetContent.newFileName) - .then(assert); +test ('Validate you are able to edit text on binary fields', async ({page}) => { + const contentUtils = new ContentUtils(page); + + await contentUtils.selectTypeOnFilter(page, fileAsset.locator); + const contentElement = await contentUtils.getContentElement(page, fileAssetContent.newFileName); + await contentElement.click(); + await waitForVisibleAndCallback(page.getByRole('heading'), () => + expect.soft(page.getByRole('heading')).toContainText(fileAsset.label) + ); + const params = { + page: page, + host: fileAssetContent.host, + editContent: true, + title: fileAssetContent.title, + action: contentProperties.publishWfAction, + binaryFileName: fileAssetContent.newFileName, + binaryFileText: fileAssetContent.newFileTextEdited + }; + await contentUtils.fillFileAssetForm(params); + await contentUtils.workflowExecutionValidationAndClose(page, 'Content saved'); + + await contentUtils.selectTypeOnFilter(page, fileAsset.locator); + await contentUtils.getContentElement(page, fileAssetContent.newFileName); + await contentElement.click(); + const editIframe= page.frameLocator(iFramesLocators.dot_edit_iframe); + await expect(editIframe.getByRole('code')).toHaveText(fileAssetContent.newFileTextEdited); + }); + /** * Test to validate the required on file asset fields */ -test("Validate the required on file asset fields", async ({ page }) => { - const detailsFrame = page.frameLocator(iFramesLocators.dot_iframe); - const contentUtils = new ContentUtils(page); - - await contentUtils.addNewContentAction( - page, - fileAsset.locator, - fileAsset.label, - ); - const params = { - page: page, - host: fileAssetContent.host, - title: fileAssetContent.title, - action: contentProperties.publishWfAction, - }; - await contentUtils.fillFileAssetForm(params); - await waitForVisibleAndCallback(detailsFrame.getByText("Error x")); - const errorMessage = detailsFrame.getByText("The field File Asset is"); - await waitForVisibleAndCallback(errorMessage, () => - expect(errorMessage).toBeVisible(), - ); +test('Validate the required on file asset fields', async ({page}) => { + const detailsFrame = page.frameLocator(iFramesLocators.dot_iframe); + const contentUtils = new ContentUtils(page); + + await contentUtils.addNewContentAction(page, fileAsset.locator, fileAsset.label); + const params = { + page: page, + host: fileAssetContent.host, + editContent: false, + title: fileAssetContent.title, + action: contentProperties.publishWfAction + } + await contentUtils.fillFileAssetForm(params); + await waitForVisibleAndCallback(detailsFrame.getByText('Error x'), async () => {}); + let errorMessage = detailsFrame.getByText('The field File Asset is'); + await waitForVisibleAndCallback(errorMessage, () => expect(errorMessage).toBeVisible()); }); /** * Test to validate the auto complete on FileName field accepting the name change */ -test("Validate the auto complete on FileName field accepting change", async ({ - page, -}) => { - const detailsFrame = page.frameLocator(iFramesLocators.dot_iframe); - const contentUtils = new ContentUtils(page); - - await contentUtils.addNewContentAction( - page, - fileAsset.locator, - fileAsset.label, - ); - await detailsFrame.locator("#fileName").fill("test"); - const params = { - page: page, - host: fileAssetContent.host, - title: fileAssetContent.title, - newFileName: fileAssetContent.newFileName, - newFileText: fileAssetContent.newFileText, - }; - await contentUtils.fillFileAssetForm(params); - const replaceText = detailsFrame.getByText("Do you want to replace the"); - await waitForVisibleAndCallback(replaceText, () => - expect(replaceText).toBeVisible(), - ); - await detailsFrame.getByLabel("Yes").click(); - await expect(detailsFrame.locator("#fileName")).toHaveValue( - fileAssetContent.newFileName, - ); +test('Validate the auto complete on FileName field accepting change', async ({page}) => { + const detailsFrame = page.frameLocator(iFramesLocators.dot_iframe); + const contentUtils = new ContentUtils(page); + + await contentUtils.addNewContentAction(page, fileAsset.locator, fileAsset.label); + await detailsFrame.locator('#fileName').fill('test'); + const params = { + page: page, + host: fileAssetContent.host, + editContent: false, + title: fileAssetContent.title, + binaryFileName: fileAssetContent.newFileName, + binaryFileText: fileAssetContent.newFileText + } + await contentUtils.fillFileAssetForm(params); + const replaceText = detailsFrame.getByText('Do you want to replace the'); + await waitForVisibleAndCallback(replaceText, () => expect(replaceText).toBeVisible()); + await detailsFrame.getByLabel('Yes').click(); + await expect(detailsFrame.locator('#fileName')).toHaveValue(fileAssetContent.newFileName); }); /** * Test to validate the auto complete on FileName field rejecting file name change */ -test("Validate the auto complete on FileName field rejecting change", async ({ - page, -}) => { - const detailsFrame = page.frameLocator(iFramesLocators.dot_iframe); - const contentUtils = new ContentUtils(page); - - await contentUtils.addNewContentAction( - page, - fileAsset.locator, - fileAsset.label, - ); - await detailsFrame.locator("#fileName").fill("test"); - const params = { - page: page, - host: fileAssetContent.host, - title: fileAssetContent.title, - newFileName: fileAssetContent.newFileName, - newFileText: fileAssetContent.newFileText, - }; - await contentUtils.fillFileAssetForm(params); - const replaceText = detailsFrame.getByText("Do you want to replace the"); - await waitForVisibleAndCallback(replaceText, async () => - expect(replaceText).toBeVisible(), - ); - await detailsFrame.getByLabel("No").click(); - await expect(detailsFrame.locator("#fileName")).toHaveValue("test"); +test('Validate the auto complete on FileName field rejecting change', async ({page}) => { + const detailsFrame = page.frameLocator(iFramesLocators.dot_iframe); + const contentUtils = new ContentUtils(page); + + await contentUtils.addNewContentAction(page, fileAsset.locator, fileAsset.label); + await detailsFrame.locator('#fileName').fill('test'); + const params = { + page: page, + host: fileAssetContent.host, + editContent: false, + title: fileAssetContent.title, + binaryFileName: fileAssetContent.newFileName, + binaryFileText: fileAssetContent.newFileText + } + await contentUtils.fillFileAssetForm(params); + const replaceText = detailsFrame.getByText('Do you want to replace the'); + await waitForVisibleAndCallback(replaceText, async () => expect(replaceText).toBeVisible()); + await detailsFrame.getByLabel('No').click(); + await expect(detailsFrame.locator('#fileName')).toHaveValue('test'); }); /** * Test to validate the title is not changing in the auto complete on FileName */ /** ENABLE AS SOON WE FIXED THE ISSUE #30945 -test('Validate the title field is not changing in the file asset auto complete', async ({page}) => { - const detailsFrame = page.frameLocator(iFramesLocators.dot_iframe); - const contentUtils = new ContentUtils(page); + test('Validate the title field is not changing in the file asset auto complete', async ({page}) => { + const detailsFrame = page.frameLocator(iFramesLocators.dot_iframe); + const contentUtils = new ContentUtils(page); - await contentUtils.addNewContentAction(page, fileAsset.locator, fileAsset.label); - await detailsFrame.locator('#title').fill('test'); - await contentUtils.fillFileAssetForm(page, fileAssetContent.host, fileAssetContent.title, null, null, null, fileAssetContent.newFileName, fileAssetContent.newFileText); + await contentUtils.addNewContentAction(page, fileAsset.locator, fileAsset.label); + await detailsFrame.locator('#title').fill('test'); + await contentUtils.fillFileAssetForm(page, fileAssetContent.host, fileAssetContent.title, null, null, null, fileAssetContent.newFileName, fileAssetContent.newFileText); - await expect(detailsFrame.locator('#title')).toHaveValue('test'); -}); -*/ + await expect(detailsFrame.locator('#title')).toHaveValue('test'); + }); + */ /** * Test to validate you are able to delete a file asset content */ -test("Delete a file asset content", async ({ page }) => { - const contentUtils = new ContentUtils(page); - // Delete the content - await contentUtils.deleteContent(page, fileAssetContent.title); +test('Delete a file asset content', async ({ page }) => { + const contentUtils = new ContentUtils(page); + // Delete the content + await contentUtils.deleteContent(page, fileAssetContent.title); }); /** * Test to validate you are able to add new pages */ -test("Add a new page", async ({ page }) => { - const contentUtils = new ContentUtils(page); - - await contentUtils.addNewContentAction( - page, - pageAsset.locator, - pageAsset.label, - ); - const params = { - page: page, - title: pageAssetContent.title, - host: pageAssetContent.host, - template: pageAssetContent.template, - friendlyName: pageAssetContent.friendlyName, - showOnMenu: pageAssetContent.showOnMenu, - sortOrder: pageAssetContent.sortOrder, - cacheTTL: pageAssetContent.cacheTTL, - action: contentProperties.publishWfAction, - }; - await contentUtils.fillPageAssetForm(params); - const dataFrame = page.frameLocator(iFramesLocators.dataTestId); - await waitForVisibleAndCallback(dataFrame.getByRole("banner")); - await expect(page.locator("ol")).toContainText( - "Pages" + pageAssetContent.title, - ); +test('Add a new page', async ({page}) => { + const contentUtils = new ContentUtils(page); + + await contentUtils.addNewContentAction(page, pageAsset.locator, pageAsset.label); + const params = { + page: page, + title: pageAssetContent.title, + host: pageAssetContent.host, + template: pageAssetContent.template, + friendlyName: pageAssetContent.friendlyName, + showOnMenu: pageAssetContent.showOnMenu, + sortOrder: pageAssetContent.sortOrder, + cacheTTL: pageAssetContent.cacheTTL, + action: contentProperties.publishWfAction, + } + await contentUtils.fillPageAssetForm(params); + const dataFrame = page.frameLocator(iFramesLocators.dataTestId); + await waitForVisibleAndCallback(dataFrame.getByRole('banner'), async () => {}); + await expect(page.locator('ol')).toContainText('Pages'+ pageAssetContent.title); }); /** * Test to validate the required fields on the page form */ -test("Validate required fields on page asset", async ({ page }) => { - const contentUtils = new ContentUtils(page); - const detailFrame = page.frameLocator(iFramesLocators.dot_iframe); - - await contentUtils.addNewContentAction( - page, - pageAsset.locator, - pageAsset.label, - ); - const params = { - page: page, - title: "", - host: pageAssetContent.host, - template: pageAssetContent.template, - showOnMenu: pageAssetContent.showOnMenu, - action: contentProperties.publishWfAction, - }; - await contentUtils.fillPageAssetForm(params); - await waitForVisibleAndCallback(detailFrame.getByText("Error x")); - - await expect( - detailFrame.getByText("The field Title is required."), - ).toBeVisible(); - await expect( - detailFrame.getByText("The field Url is required."), - ).toBeVisible(); - await expect( - detailFrame.getByText("The field Friendly Name is"), - ).toBeVisible(); +test('Validate required fields on page asset', async ({page}) => { + const contentUtils = new ContentUtils(page); + const detailFrame = page.frameLocator(iFramesLocators.dot_iframe); + + await contentUtils.addNewContentAction(page, pageAsset.locator, pageAsset.label); + const params = { + page: page, + title: "", + host: pageAssetContent.host, + template: pageAssetContent.template, + showOnMenu: pageAssetContent.showOnMenu, + action: contentProperties.publishWfAction + } + await contentUtils.fillPageAssetForm(params); + await waitForVisibleAndCallback(detailFrame.getByText('Error x'), async () => {}); + + await expect(detailFrame.getByText('The field Title is required.')).toBeVisible(); + await expect(detailFrame.getByText('The field Url is required.')).toBeVisible(); + await expect(detailFrame.getByText('The field Friendly Name is')).toBeVisible(); }); /** * Test to validate the auto generation of fields on page asset */ -test("Validate auto generation of fields on page asset", async ({ page }) => { - const contentUtils = new ContentUtils(page); - const detailFrame = page.frameLocator(iFramesLocators.dot_iframe); - - await contentUtils.addNewContentAction( - page, - pageAsset.locator, - pageAsset.label, - ); - const params = { - page: page, - title: pageAssetContent.title, - host: pageAssetContent.host, - template: pageAssetContent.template, - showOnMenu: pageAssetContent.showOnMenu, - }; - await contentUtils.fillPageAssetForm(params); - - await expect(detailFrame.locator("#url")).toHaveValue( - pageAssetContent.title.toLowerCase(), - ); - await expect(detailFrame.locator("#friendlyName")).toHaveValue( - pageAssetContent.title, - ); +test('Validate auto generation of fields on page asset', async ({page}) => { + const contentUtils = new ContentUtils(page); + const detailFrame = page.frameLocator(iFramesLocators.dot_iframe); + + await contentUtils.addNewContentAction(page, pageAsset.locator, pageAsset.label); + const params = { + page: page, + title: pageAssetContent.title, + host: pageAssetContent.host, + template: pageAssetContent.template, + showOnMenu: pageAssetContent.showOnMenu, + } + await contentUtils.fillPageAssetForm(params); + + await expect(detailFrame.locator('#url')).toHaveValue(pageAssetContent.title.toLowerCase()); + await expect(detailFrame.locator('#friendlyName')).toHaveValue(pageAssetContent.title); }); + + + + + diff --git a/e2e/dotcms-e2e-node/frontend/tests/login/login.spec.ts b/e2e/dotcms-e2e-node/frontend/tests/login/login.spec.ts index c42804890012..3b7a07021d81 100644 --- a/e2e/dotcms-e2e-node/frontend/tests/login/login.spec.ts +++ b/e2e/dotcms-e2e-node/frontend/tests/login/login.spec.ts @@ -1,49 +1,47 @@ -import { test, expect } from "@playwright/test"; -import { admin1, wrong1, wrong2 } from "./credentialsData"; +import {test, expect} from '@playwright/test'; +import {admin1, wrong1, wrong2} from './credentialsData'; + const validCredentials = [ - { username: admin1.username, password: admin1.password }, // admin user + {username: admin1.username, password: admin1.password}, // admin user ]; /** * Test to validate the login functionality with valid credentials */ -validCredentials.forEach(({ username, password }) => { - test(`Login with Valid Credentials: ${username}`, async ({ page }) => { - await page.goto("/dotAdmin"); - - await page.fill('input[id="inputtext"]', username); - await page.fill('input[id="password"]', password); - await page.getByTestId("submitButton").click(); - - // Assertion and further test steps - await expect( - page.getByRole("link", { name: "Getting Started" }), - ).toBeVisible(); - }); +validCredentials.forEach(({username, password}) => { + test(`Login with Valid Credentials: ${username}`, async ({page}) => { + await page.goto('/dotAdmin'); + + await page.fill('input[id="inputtext"]', username); + await page.fill('input[id="password"]', password); + await page.getByTestId('submitButton').click(); + + // Assertion and further test steps + await expect(page.getByRole('link', {name: 'Getting Started'})).toBeVisible(); + }); }); + const invalidCredentials = [ - { username: wrong1.username, password: wrong1.password }, // Valid username, invalid password - { username: wrong2.username, password: wrong2.password }, // Invalid username, valid password + {username: wrong1.username, password: wrong1.password}, // Valid username, invalid password + {username: wrong2.username, password: wrong2.password}, // Invalid username, valid password ]; /** * Test to validate the login functionality with invalid credentials */ -invalidCredentials.forEach((credentials) => { - test(`Login with invalid Credentials: ${credentials.username}`, async ({ - page, - }) => { - const { username, password } = credentials; +invalidCredentials.forEach(credentials => { + test(`Login with invalid Credentials: ${credentials.username}`, async ({page}) => { + const {username, password} = credentials; - await page.goto("/dotAdmin"); + await page.goto('/dotAdmin'); - await page.fill('input[id="inputtext"]', username); - await page.fill('input[id="password"]', password); - await page.getByTestId("submitButton").click(); + await page.fill('input[id="inputtext"]', username); + await page.fill('input[id="password"]', password); + await page.getByTestId('submitButton').click(); - // Assertion and further test steps - await expect(page.getByTestId("message")).toBeVisible({ timeout: 30000 }); - }); -}); + // Assertion and further test steps + await expect(page.getByTestId('message')).toBeVisible({timeout: 30000}); + }); +}); \ No newline at end of file diff --git a/e2e/dotcms-e2e-node/frontend/utils/contentUtils.ts b/e2e/dotcms-e2e-node/frontend/utils/contentUtils.ts index 1ee4098c8bf6..fae9639e731f 100644 --- a/e2e/dotcms-e2e-node/frontend/utils/contentUtils.ts +++ b/e2e/dotcms-e2e-node/frontend/utils/contentUtils.ts @@ -1,443 +1,366 @@ -import { expect, FrameLocator, Locator, Page } from "@playwright/test"; -import { - contentGeneric, - iFramesLocators, - fileAsset, - pageAsset, -} from "../locators/globalLocators"; -import { waitForVisibleAndCallback } from "./dotCMSUtils"; -import { contentProperties } from "../tests/contentSearch/contentData"; +import {expect, FrameLocator, Locator, Page} from '@playwright/test'; +import {contentGeneric, iFramesLocators, fileAsset, pageAsset} from '../locators/globalLocators'; +import {waitForVisibleAndCallback} from './dotCMSUtils'; +import {contentProperties, fileAssetContent} from "../tests/contentSearch/contentData"; + export class ContentUtils { - page: Page; - - constructor(page: Page) { - this.page = page; - } - - /** - * Fill the rich text form - * @param page - * @param title - * @param body - * @param action - */ - async fillRichTextForm( - page: Page, - title: string, - body: string, - action: string, - ) { - const dotIframe = page.frameLocator(iFramesLocators.dot_iframe); - - const headingLocator = page.getByRole("heading"); - await waitForVisibleAndCallback(headingLocator, () => - expect.soft(headingLocator).toContainText(contentGeneric.label), - ); - - //Fill title - await dotIframe.locator("#title").fill(title); - //Fill body - await dotIframe.locator("#block-editor-body div").nth(1).fill(body); - //Click on action - await dotIframe.getByText(action).first().click(); - } - - /** - * Fill the file asset form - * @param params - */ - async fillFileAssetForm(params: FileAssetFormParams) { - const { page, host, title, action, fromURL, newFileName, newFileText } = - params; - const dotIframe = page.frameLocator(iFramesLocators.dot_iframe); - - await waitForVisibleAndCallback(page.getByRole("heading"), () => - expect.soft(page.getByRole("heading")).toContainText(fileAsset.label), - ); - - await dotIframe.locator("#HostSelector-hostFolderSelect").fill(host); - - if (newFileName && newFileText) { - await dotIframe - .getByRole("button", { name: " Create New File" }) - .click(); - 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(); - await waitForVisibleAndCallback(dotIframe.getByRole("button", { name: " Remove" })); + page: Page; + + constructor(page: Page) { + this.page = page; } - await waitForVisibleAndCallback(dotIframe.locator("#title"), () => - dotIframe.locator("#title").fill(title), - ); + /** + * Fill the rich text form + * @param page + * @param title + * @param body + * @param action + */ + async fillRichTextForm(page: Page, title: string, body: string, action: string) { + const dotIframe = page.frameLocator(iFramesLocators.dot_iframe); + + const headingLocator = page.getByRole('heading'); + await waitForVisibleAndCallback(headingLocator, () => expect.soft(headingLocator).toContainText(contentGeneric.label)); + + //Fill title + await dotIframe.locator('#title').fill(title); + //Fill body + await dotIframe.locator('#block-editor-body div').nth(1).fill(body); + //Click on action + await dotIframe.getByText(action).first().click(); + } - if (action) { - await dotIframe.getByText(action).first().click(); + /** + * Fill the file asset form + * @param params + */ + async fillFileAssetForm(params: FileAssetFormParams) { + const { page, host, editContent, title, action, fromURL, binaryFileName, binaryFileText } = params; + const dotIframe = page.frameLocator(iFramesLocators.dot_iframe); + + if (binaryFileName && binaryFileText) { + if (editContent) { + const editFrame = page.frameLocator(iFramesLocators.dot_edit_iframe); + await editFrame.getByRole('button', { name: ' Edit' }).click(); + await waitForVisibleAndCallback(editFrame.getByLabel('Editor content;Press Alt+F1'), async () => {}); + await editFrame.getByLabel('Editor content;Press Alt+F1').clear(); + await editFrame.getByLabel('Editor content;Press Alt+F1').fill(fileAssetContent.newFileTextEdited); + await editFrame.getByRole('button', { name: 'Save' }).click(); + } else { + await waitForVisibleAndCallback(page.getByRole('heading'), async () => { + expect.soft(page.getByRole('heading')).toContainText(fileAsset.label); + }); + await dotIframe.locator('#HostSelector-hostFolderSelect').fill(host); + await dotIframe.getByRole('button', { name: ' Create New File' }).click(); + await dotIframe.getByTestId('editor-file-name').fill(binaryFileName); + await dotIframe.getByLabel('Editor content;Press Alt+F1').fill(binaryFileText); + await dotIframe.getByRole('button', { name: 'Save' }).click(); + } + } + + 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(); + await waitForVisibleAndCallback(dotIframe.getByRole('button', { name: ' Remove' }), async () => {}); + } + + await waitForVisibleAndCallback(dotIframe.locator('#title'), async () => { + await dotIframe.locator('#title').fill(title); + }); + + if (action) { + await dotIframe.getByText(action).first().click(); + } } - } - - /** - * Validate the workflow execution and close the modal - * @param page - * @param message - */ - async workflowExecutionValidationAndClose(page: Page, message: string) { - const dotIframe = page.frameLocator(iFramesLocators.dot_iframe); - - const executionConfirmation = dotIframe.getByText(message); - await waitForVisibleAndCallback(executionConfirmation, () => - expect(executionConfirmation).toBeVisible(), - ); - await expect(executionConfirmation).toBeHidden(); - //Click on close - const closeBtnLocator = page - .getByTestId("close-button") - .getByRole("button"); - await waitForVisibleAndCallback(closeBtnLocator, () => - closeBtnLocator.click(), - ); - } - - /** - * Add new content action on the content portlet - * @param page - * @param typeLocator - * @param typeString - */ - 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(), - ); - await this.selectTypeOnFilter(page, typeLocator); - - await waitForVisibleAndCallback( - iframe.locator("#dijit_form_DropDownButton_0"), - () => iframe.locator("#dijit_form_DropDownButton_0").click(), - ); - await waitForVisibleAndCallback(iframe.getByLabel("actionPrimaryMenu")); - await iframe.getByLabel("▼").getByText("Add New Content").click(); - const headingLocator = page.getByRole("heading"); - await waitForVisibleAndCallback(headingLocator, () => - expect(headingLocator).toHaveText(typeString), - ); - } - - /** - * Select content type on filter on the content portlet - * @param page - * @param typeLocator - */ - async selectTypeOnFilter(page: Page, typeLocator: string) { - const iframe = page.frameLocator(iFramesLocators.main_iframe); - - const structureINodeDivLocator = iframe - .locator("#widget_structure_inode div") - .first(); - await waitForVisibleAndCallback(structureINodeDivLocator, () => - structureINodeDivLocator.click(), - ); - const typeLocatorByTextLocator = iframe.getByText(typeLocator); - await waitForVisibleAndCallback(typeLocatorByTextLocator, () => - typeLocatorByTextLocator.click(), - ); - } - - /** - * Show query on the content portlet - * @param iframe - */ - async showQuery(iframe: FrameLocator) { - const createOptionsBtnLocator = iframe.getByRole("button", { - name: "createOptions", - }); - await waitForVisibleAndCallback(createOptionsBtnLocator, () => - createOptionsBtnLocator.click(), - ); - - //Validate the search button has a sub-menu - await expect( - iframe.getByLabel("Search ▼").getByText("Search"), - ).toBeVisible(); - await expect(iframe.getByText("Show Query")).toBeVisible(); - - // Click on show query - await iframe.getByText("Show Query").click(); - } - - /** - * Validate if the content exists in the results table on the content portlet - * @param page - * @param text - */ - async validateContentExist(page: Page, text: string) { - const iframe = page.frameLocator(iFramesLocators.main_iframe); - - await waitForVisibleAndCallback(iframe.locator("#results_table tbody tr:nth-of-type(2)")); - await page.waitForTimeout(1000); - - const cells = iframe.locator("#results_table tbody tr:nth-of-type(2) td"); - const cellCount = await cells.count(); - - for (let j = 0; j < cellCount; j++) { - const cell = cells.nth(j); - const cellText = await cell.textContent(); - - if (cellText && cellText.includes(text)) { - console.log(`The text "${text}" exists in the results table.`); - return true; - } + + /** + * Validate the workflow execution and close the modal + * @param page + * @param message + */ + async workflowExecutionValidationAndClose(page: Page, message: string) { + const dotIframe = page.frameLocator(iFramesLocators.dot_iframe); + + const executionConfirmation = dotIframe.getByText(message); + await waitForVisibleAndCallback(executionConfirmation, () => expect(executionConfirmation).toBeVisible()); + await expect(executionConfirmation).toBeHidden(); + //Click on close + const closeBtnLocator = page.getByTestId('close-button').getByRole('button'); + await waitForVisibleAndCallback(closeBtnLocator, () => closeBtnLocator.click()); } - console.log(`The text "${text}" does not exist in the results table.`); - return false; - } - - /** - * Get the content element from the results table on the content portlet - * @param page - * @param title - */ - async getContentElement(page: Page, title: string): Promise { - const iframe = page.frameLocator(iFramesLocators.main_iframe); - - await iframe - .locator("#results_table tbody tr") - .first() - .waitFor({ state: "visible" }); - const rows = iframe.locator("#results_table tbody tr"); - const rowCount = await rows.count(); - - for (let i = 0; i < rowCount; i++) { - const secondCell = rows.nth(i).locator("td:nth-of-type(2)"); - const element = secondCell.locator(`a:text("${title}")`); - - if ((await element.count()) > 0) { - return element.first(); - } + /** + * Add new content action on the content portlet + * @param page + * @param typeLocator + * @param typeString + */ + 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()); + await this.selectTypeOnFilter(page, typeLocator); + + await waitForVisibleAndCallback(iframe.locator('#dijit_form_DropDownButton_0'), () => iframe.locator('#dijit_form_DropDownButton_0').click()); + await waitForVisibleAndCallback(iframe.getByLabel('actionPrimaryMenu'), async () => {}); + await iframe.getByLabel('▼').getByText('Add New Content').click(); + const headingLocator = page.getByRole('heading'); + await waitForVisibleAndCallback(headingLocator, () => expect(headingLocator).toHaveText(typeString)); + }; + + /** + * Select content type on filter on the content portlet + * @param page + * @param typeLocator + */ + async selectTypeOnFilter(page: Page, typeLocator: string) { + const iframe = page.frameLocator(iFramesLocators.main_iframe); + + const structureINodeDivLocator = iframe.locator('#widget_structure_inode div').first(); + await waitForVisibleAndCallback(structureINodeDivLocator, () => structureINodeDivLocator.click()); + const typeLocatorByTextLocator = iframe.getByText(typeLocator); + await waitForVisibleAndCallback(typeLocatorByTextLocator, () => typeLocatorByTextLocator.click()); + await page.waitForLoadState(); } - console.log(`The content with the title ${title} does not exist`); - return null; - } - - /** - * Edit content on the content portlet - * @param page - * @param title - * @param newTitle - * @param newBody - * @param action - */ - async editContent( - page: Page, - title: string, - newTitle: string, - newBody: string, - action: string, - ) { - const contentElement = await this.getContentElement(page, title); - if (contentElement) { - await contentElement.click(); - } else { - console.log("Content not found"); - return; + + /** + * Show query on the content portlet + * @param iframe + */ + async showQuery(iframe: FrameLocator) { + const createOptionsBtnLocator = iframe.getByRole('button', {name: 'createOptions'}); + await waitForVisibleAndCallback(createOptionsBtnLocator, () => createOptionsBtnLocator.click()); + + //Validate the search button has a sub-menu + await expect(iframe.getByLabel('Search ▼').getByText('Search')).toBeVisible(); + await expect(iframe.getByText('Show Query')).toBeVisible(); + + // Click on show query + await iframe.getByText('Show Query').click(); } - await this.fillRichTextForm(page, newTitle, newBody, action); - await this.workflowExecutionValidationAndClose(page, "Content saved"); - } - - /** - * Delete content on the content portlet - * @param page - * @param title - */ - async deleteContent(page: Page, title: string) { - const iframe = page.frameLocator(iFramesLocators.main_iframe); - - while ((await this.getContentState(page, title)) !== null) { - const contentState = await this.getContentState(page, title); - - if (contentState === "published") { - await this.performWorkflowAction( - page, - title, - contentProperties.unpublishWfAction, - ); - } else if (contentState === "draft") { - await this.performWorkflowAction( - page, - title, - contentProperties.archiveWfAction, - ); - await iframe.getByRole("link", { name: "Advanced" }).click(); - await iframe.locator("#widget_showingSelect div").first().click(); - const dropDownMenu = iframe.getByRole("option", { name: "Archived" }); - await waitForVisibleAndCallback(dropDownMenu, () => - dropDownMenu.click(), - ); + + /** + * Validate if the content exists in the results table on the content portlet + * @param page + * @param text + */ + async validateContentExist(page: Page, text: string) { + const iframe = page.frameLocator(iFramesLocators.main_iframe); + + await waitForVisibleAndCallback(iframe.locator('#results_table tbody tr:nth-of-type(2)'), async () => { + }); await page.waitForTimeout(1000); - } else if (contentState === "archived") { - await this.performWorkflowAction( - page, - title, - contentProperties.deleteWfAction, - ); - return; - } - await page.waitForLoadState(); + const cells = iframe.locator('#results_table tbody tr:nth-of-type(2) td'); + const cellCount = await cells.count(); + + for (let j = 0; j < cellCount; j++) { + const cell = cells.nth(j); + const cellText = await cell.textContent(); + + if (cellText && cellText.includes(text)) { + console.log(`The text "${text}" exists in the results table.`); + return true; + } + } + + console.log(`The text "${text}" does not exist in the results table.`); + return false; } - } - - /** - * Perform workflow action for some specific content - * @param page - * @param title - * @param action - */ - async performWorkflowAction(page: Page, title: string, action: string) { - const iframe = page.frameLocator(iFramesLocators.main_iframe); - const contentElement = await this.getContentElement(page, title); - if (contentElement) { - await contentElement.click({ - button: "right", - }); + + /** + * Get the content element from the results table on the content portlet + * @param page + * @param title + */ + async getContentElement(page: Page, title: string): Promise { + const iframe = page.frameLocator(iFramesLocators.main_iframe); + + await iframe.locator('#results_table tbody tr').first().waitFor({state: 'visible'}); + const rows = iframe.locator('#results_table tbody tr'); + const rowCount = await rows.count(); + + for (let i = 0; i < rowCount; i++) { + const secondCell = rows.nth(i).locator('td:nth-of-type(2)'); + const element = secondCell.locator(`a:text("${title}")`); + + if (await element.count() > 0) { + return element.first(); + } + } + console.log(`The content with the title ${title} does not exist`); + return null; } - const actionBtnLocator = iframe.getByRole("menuitem", { name: action }); - await waitForVisibleAndCallback(actionBtnLocator, () => - actionBtnLocator.getByText(action).click(), - ); - const executionConfirmation = iframe.getByText("Workflow executed"); - await expect(executionConfirmation).toBeVisible() - await waitForVisibleAndCallback(executionConfirmation, () => - expect(executionConfirmation).toBeVisible(), - ); - await waitForVisibleAndCallback(executionConfirmation, () => - expect(executionConfirmation).toBeHidden(), - ); - } - - /** - * Get the content state from the results table on the content portlet - * @param page - * @param title - */ - 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" }); - const rows = iframe.locator("#results_table tbody tr"); - const rowCount = await rows.count(); - - for (let i = 0; i < rowCount; i++) { - const secondCell = rows.nth(i).locator("td:nth-of-type(2)"); - const element = secondCell.locator(`a:text("${title}")`); - - if ((await element.count()) > 0) { - const stateColumn = rows.nth(i).locator("td:nth-of-type(3)"); - const targetDiv = stateColumn.locator("div#icon"); - return await targetDiv.getAttribute("class"); - } + + + /** + * Edit content on the content portlet + * @param page + * @param title + * @param newTitle + * @param newBody + * @param action + */ + async editContent(page: Page, title: string, newTitle: string, newBody: string, action: string) { + const contentElement = await this.getContentElement(page, title); + if (contentElement) { + await contentElement.click(); + } else { + console.log('Content not found'); + return; + } + await this.fillRichTextForm(page, newTitle, newBody, action); + await this.workflowExecutionValidationAndClose(page, 'Content saved'); + } + + /** + * Delete content on the content portlet + * @param page + * @param title + */ + async deleteContent(page: Page, title: string) { + const iframe = page.frameLocator(iFramesLocators.main_iframe); + + while (await this.getContentState(page, title) !== null) { + const contentState = await this.getContentState(page, title); + + if (contentState === 'published') { + await this.performWorkflowAction(page, title, contentProperties.unpublishWfAction); + } else if (contentState === 'draft') { + await this.performWorkflowAction(page, title, contentProperties.archiveWfAction); + await iframe.getByRole('link', {name: 'Advanced'}).click(); + 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; + } + + await page.waitForLoadState(); + } } - console.log("Content not found"); - return null; - } - - /** - * Fill the pageAsset form - * @param params - */ - async fillPageAssetForm(params: PageAssetFormParams) { - const { - page, - title, - action, - url, - host, - template, - friendlyName, - showOnMenu, - sortOrder, - cacheTTL, - } = params; - const dotIframe = page.frameLocator(iFramesLocators.dot_iframe); - - await waitForVisibleAndCallback(page.getByRole("heading"), () => - expect.soft(page.getByRole("heading")).toContainText(pageAsset.label), - ); - await dotIframe.locator("#titleBox").fill(title); - - if (url) await dotIframe.locator("#url").fill(url); - if (host) { - await dotIframe.locator("#hostFolder_field div").nth(2).click(); - await dotIframe.getByRole("treeitem", { name: host }).click(); + /** + * Perform workflow action for some specific content + * @param page + * @param title + * @param action + */ + async performWorkflowAction(page: Page, title: string, action: string) { + const iframe = page.frameLocator(iFramesLocators.main_iframe); + const contentElement = await this.getContentElement(page, title); + if (contentElement) { + await contentElement.click({ + button: 'right' + }); + } + const actionBtnLocator = iframe.getByRole('menuitem', {name: action}); + await waitForVisibleAndCallback(actionBtnLocator, () => actionBtnLocator.getByText(action).click()); + const executionConfirmation = iframe.getByText('Workflow executed'); + await waitForVisibleAndCallback(executionConfirmation, () => expect(executionConfirmation).toBeVisible()); + await waitForVisibleAndCallback(executionConfirmation, () => expect(executionConfirmation).toBeHidden()); } - if (template) { - await dotIframe.locator("#widget_templateSel div").first().click(); - await dotIframe.getByText(template).click(); + + /** + * Get the content state from the results table on the content portlet + * @param page + * @param title + */ + 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'}); + const rows = iframe.locator('#results_table tbody tr'); + const rowCount = await rows.count(); + + for (let i = 0; i < rowCount; i++) { + const secondCell = rows.nth(i).locator('td:nth-of-type(2)'); + const element = secondCell.locator(`a:text("${title}")`); + + if (await element.count() > 0) { + const stateColumn = rows.nth(i).locator('td:nth-of-type(3)'); + const targetDiv = stateColumn.locator('div#icon'); + return await targetDiv.getAttribute('class'); + } + } + + console.log('Content not found'); + return null; + } + + + /** + * Fill the pageAsset form + * @param params + */ + async fillPageAssetForm(params: PageAssetFormParams) { + const { page, title, action, url, host, template, friendlyName, showOnMenu, sortOrder, cacheTTL } = params; + const dotIframe = page.frameLocator(iFramesLocators.dot_iframe); + + await waitForVisibleAndCallback(page.getByRole('heading'), () => + expect.soft(page.getByRole('heading')).toContainText(pageAsset.label) + ); + await dotIframe.locator('#titleBox').fill(title); + + if (url) await dotIframe.locator('#url').fill(url); + if (host) { + await dotIframe.locator('#hostFolder_field div').nth(2).click(); + await dotIframe.getByRole('treeitem', { name: host }).click(); + } + if (template) { + await dotIframe.locator('#widget_templateSel div').first().click(); + await dotIframe.getByText(template).click(); + } + if (friendlyName) await dotIframe.locator('#friendlyName').fill(friendlyName); + if (showOnMenu) await dotIframe.getByLabel('Content', { exact: true }).getByLabel('').check(); + if (sortOrder) await dotIframe.locator('#sortOrder').fill(sortOrder); + if (cacheTTL) await dotIframe.locator('#cachettlbox').fill(cacheTTL.toString()); + if (action) await dotIframe.getByText(action).first().click(); } - if (friendlyName) - await dotIframe.locator("#friendlyName").fill(friendlyName); - if (showOnMenu) - await dotIframe - .getByLabel("Content", { exact: true }) - .getByLabel("") - .check(); - if (sortOrder) await dotIframe.locator("#sortOrder").fill(sortOrder); - if (cacheTTL) - await dotIframe.locator("#cachettlbox").fill(cacheTTL.toString()); - if (action) await dotIframe.getByText(action).first().click(); - } + + } /** * Base form params */ interface BaseFormParams { - page: Page; - title: string; - action?: string; + page: Page; + title: string; + action?: string; } /** * Parameter to fill the file asset form params */ interface FileAssetFormParams extends BaseFormParams { - host: string; - fileName?: string; - fromURL?: string; - newFileName?: string; - newFileText?: string; + host: string; + editContent : boolean; + fileName?: string; + fromURL?: string; + binaryFileName?: string; + binaryFileText?: string; } /** * Parameter to fill the page asset form params */ interface PageAssetFormParams extends BaseFormParams { - url?: string; - host?: string; - template?: string; - friendlyName?: string; - showOnMenu?: boolean; - sortOrder?: string; - cacheTTL?: number; + url?: string; + host?: string; + template?: string; + friendlyName?: string; + showOnMenu?: boolean; + sortOrder?: string; + cacheTTL?: number; } + + + + diff --git a/e2e/dotcms-e2e-node/frontend/utils/dotCMSUtils.ts b/e2e/dotcms-e2e-node/frontend/utils/dotCMSUtils.ts index c2dc5d670343..2ab35d19b792 100644 --- a/e2e/dotcms-e2e-node/frontend/utils/dotCMSUtils.ts +++ b/e2e/dotcms-e2e-node/frontend/utils/dotCMSUtils.ts @@ -1,49 +1,38 @@ -import { Page, expect, Locator } from "@playwright/test"; -import { loginLocators } from "../locators/globalLocators"; +import { Page, expect, Locator } from '@playwright/test'; +import { loginLocators } from '../locators/globalLocators'; export class dotCMSUtils { - page: Page; - /** - * Login to dotCMS - * @param page - * @param username - * @param password - */ - async login(page: Page, username: string, password: string) { - await page.goto("/dotAdmin"); - await page.waitForLoadState(); - const userNameInputLocator = page.locator(loginLocators.userNameInput); - await waitForVisibleAndCallback(userNameInputLocator, () => - userNameInputLocator.fill(username), - ); - const passwordInputLocator = page.locator(loginLocators.passwordInput); - await waitForVisibleAndCallback(passwordInputLocator, () => - passwordInputLocator.fill(password), - ); - const loginBtnLocator = page.getByTestId(loginLocators.loginBtn); - await waitForVisibleAndCallback(loginBtnLocator, () => - loginBtnLocator.click(), - ); - const gettingStartedLocator = page.getByRole("link", { - name: "Getting Started", - }); - await waitForVisibleAndCallback(gettingStartedLocator, () => - expect(gettingStartedLocator).toBeVisible(), - ); - } + /** + * Login to dotCMS + * @param page + * @param username + * @param password + */ + async login(page: Page, username: string, password: string) { + await page.goto('/dotAdmin'); + await page.waitForLoadState() + const userNameInputLocator = page.locator(loginLocators.userNameInput); + await waitForVisibleAndCallback(userNameInputLocator, () => userNameInputLocator.fill(username)); + const passwordInputLocator = page.locator(loginLocators.passwordInput); + await waitForVisibleAndCallback(passwordInputLocator, () => passwordInputLocator.fill(password)); + const loginBtnLocator = page.getByTestId(loginLocators.loginBtn); + await waitForVisibleAndCallback(loginBtnLocator, () => loginBtnLocator.click()); + const gettingStartedLocator = page.getByRole('link', { name: 'Getting Started' }); + await waitForVisibleAndCallback(gettingStartedLocator, () => expect(gettingStartedLocator).toBeVisible()); + } - /** - * Navigate to the content portlet providing the menu, group and tool locators - * @param menu - * @param group - * @param tool - */ - async navigate(menu: Locator, group: Locator, tool: Locator) { - await menu.click(); - await group.click(); - await tool.click(); - } + /** + * Navigate to the content portlet providing the menu, group and tool locators + * @param menu + * @param group + * @param tool + */ + async navigate(menu : Locator, group : Locator, tool : Locator) { + await menu.click(); + await group.click(); + await tool.click(); + } } /** @@ -51,12 +40,9 @@ export class dotCMSUtils { * @param locator * @param state */ -export const waitFor = async ( - locator: Locator, - state: "attached" | "detached" | "visible" | "hidden", -): Promise => { - await locator.waitFor({ state: 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 @@ -64,15 +50,9 @@ export const waitFor = async ( * @param state * @param callback */ -export const waitForAndCallback = async ( - locator: Locator, - state: "attached" | "detached" | "visible" | "hidden", - callback?: () => Promise, -): Promise => { - await waitFor(locator, state); - if (callback) { +export const waitForAndCallback = async (locator: Locator, state: "attached" | "detached" | "visible" | "hidden", callback: () => Promise): Promise => { + await waitFor(locator, state); await callback(); - } }; /** @@ -80,9 +60,6 @@ export const waitForAndCallback = async ( * @param locator * @param callback */ -export const waitForVisibleAndCallback = async ( - locator: Locator, - callback?: () => Promise, -): Promise => { - await waitForAndCallback(locator, "visible", callback); -}; +export const waitForVisibleAndCallback = async (locator: Locator, callback: () => Promise): Promise => { + await waitForAndCallback(locator, 'visible', callback); +}; \ No newline at end of file From c8c971bce702ffbfd68813cbb146166d6298fc57 Mon Sep 17 00:00:00 2001 From: Jonathan Date: Tue, 7 Jan 2025 15:11:20 -0600 Subject: [PATCH 2/2] #30950 QA feedback (#31020) Adding QA feedback --- .../business/ESContentletAPIImpl.java | 6 ++ .../business/LanguageFactory.java | 4 +- .../postman/Workflow_Resource_Tests.json | 77 ++++++++++++++++++- 3 files changed, 83 insertions(+), 4 deletions(-) diff --git a/dotCMS/src/main/java/com/dotcms/content/elasticsearch/business/ESContentletAPIImpl.java b/dotCMS/src/main/java/com/dotcms/content/elasticsearch/business/ESContentletAPIImpl.java index d6ceb97dd977..65a0425a0beb 100644 --- a/dotCMS/src/main/java/com/dotcms/content/elasticsearch/business/ESContentletAPIImpl.java +++ b/dotCMS/src/main/java/com/dotcms/content/elasticsearch/business/ESContentletAPIImpl.java @@ -7401,6 +7401,7 @@ public void validateContentlet(final Contentlet contentlet, final List throw new DotContentletValidationException("The contentlet must not be null."); } final String contentTypeId = contentlet.getContentTypeId(); + final long languageId = contentlet.getLanguageId(); final String contentIdentifier = (UtilMethods.isSet(contentlet.getIdentifier()) ? contentlet.getIdentifier() : "Unknown/New"); @@ -7409,6 +7410,11 @@ public void validateContentlet(final Contentlet contentlet, final List "Contentlet [" + contentIdentifier + "] is not associated to " + "any Content Type."); } + if (languageId > 0 && !this.languageAPI.hasLanguage(languageId)) { + throw new DotContentletValidationException( + "Contentlet [" + contentIdentifier + "] is associated to an invalid language id: " + languageId); + } + final ContentType contentType = Sneaky.sneak( () -> APILocator.getContentTypeAPI(APILocator.systemUser()).find (contentTypeId)); diff --git a/dotCMS/src/main/java/com/dotmarketing/portlets/languagesmanager/business/LanguageFactory.java b/dotCMS/src/main/java/com/dotmarketing/portlets/languagesmanager/business/LanguageFactory.java index 5921effb3e23..55387b556e15 100644 --- a/dotCMS/src/main/java/com/dotmarketing/portlets/languagesmanager/business/LanguageFactory.java +++ b/dotCMS/src/main/java/com/dotmarketing/portlets/languagesmanager/business/LanguageFactory.java @@ -107,9 +107,9 @@ protected abstract void transferAssets(final Long oldDefaultLanguage, final Long protected abstract boolean hasLanguage(String id); /** - * + * Returns the language object for a specific language id. * @param id - * @return + * @return true if has language */ protected abstract boolean hasLanguage(long id); diff --git a/dotcms-postman/src/main/resources/postman/Workflow_Resource_Tests.json b/dotcms-postman/src/main/resources/postman/Workflow_Resource_Tests.json index bb61a687a986..2b3640fbfebe 100644 --- a/dotcms-postman/src/main/resources/postman/Workflow_Resource_Tests.json +++ b/dotcms-postman/src/main/resources/postman/Workflow_Resource_Tests.json @@ -18411,7 +18411,7 @@ } }, "url": { - "raw": "{{serverURL}}/api/v1/workflow/actions/ceca71a0-deee-4999-bd47-b01baa1bcfc8/fire?identifier={{englishidentifier}}&lang=1&indexPolicy=WAIT_FOR ", + "raw": "{{serverURL}}/api/v1/workflow/actions/ceca71a0-deee-4999-bd47-b01baa1bcfc8/fire?identifier={{englishidentifier}}&language=1&indexPolicy=WAIT_FOR ", "host": [ "{{serverURL}}" ], @@ -18429,7 +18429,80 @@ "value": "{{englishidentifier}}" }, { - "key": "lang", + "key": "language", + "value": "1" + }, + { + "key": "indexPolicy", + "value": "WAIT_FOR " + } + ] + } + }, + "response": [] + }, + { + "name": "CreateContentOnNonExistingLang", + "event": [ + { + "listen": "test", + "script": { + "exec": [ + "let jsonData = pm.response.json();", + "", + "pm.test(\"Invalid request\", function () {", + " pm.response.to.have.status(400);", + "});", + "", + "" + ], + "type": "text/javascript", + "packages": {} + } + } + ], + "request": { + "auth": { + "type": "bearer", + "bearer": [ + { + "key": "token", + "value": "{{jwt}}", + "type": "string" + } + ] + }, + "method": "PUT", + "header": [], + "body": { + "mode": "raw", + "raw": "{\n \"contentlet\":{\n \"identifier\":\"{{englishidentifier}}\",\n \"languageId\":\"99999999999999\",\n \"contentType\":\"webPageContent\",\n \"title\":\"TestNonExistingLang\",\n \"contentHost\":\"default\",\n \"body\":\"Non existing language\"\n }\n}", + "options": { + "raw": { + "language": "json" + } + } + }, + "url": { + "raw": "{{serverURL}}/api/v1/workflow/actions/ceca71a0-deee-4999-bd47-b01baa1bcfc8/fire?identifier={{englishidentifier}}&language=1&indexPolicy=WAIT_FOR ", + "host": [ + "{{serverURL}}" + ], + "path": [ + "api", + "v1", + "workflow", + "actions", + "ceca71a0-deee-4999-bd47-b01baa1bcfc8", + "fire" + ], + "query": [ + { + "key": "identifier", + "value": "{{englishidentifier}}" + }, + { + "key": "language", "value": "1" }, {