From 4982b98814117ff62aef52222bcb1948652d75e9 Mon Sep 17 00:00:00 2001 From: Nicolas Molina Date: Thu, 2 Jan 2025 17:15:09 -0400 Subject: [PATCH 01/25] e2e: setup for edit content --- e2e/dotcms-e2e-node/frontend/index.js | 114 +-- .../frontend/locators/globalLocators.ts | 49 +- .../locators/navigation/menuLocators.ts | 61 +- e2e/dotcms-e2e-node/frontend/package.json | 6 +- .../pages/listingContentTypes.page.ts | 12 + .../frontend/pages/listngContent.page.ts | 26 + .../frontend/pages/textField.page.ts | 12 + .../frontend/playwright.config.ts | 49 +- .../tests/contentSearch/contentData.ts | 61 +- .../contentSearch/contentEditing.spec.ts | 534 ++++++++----- .../contentSearch/portletIntegrity.spec.ts | 353 +++++---- .../frontend/tests/login/credentialsData.ts | 29 +- .../frontend/tests/login/login.spec.ts | 60 +- .../frontend/tests/login/translations.spec.ts | 48 +- e2e/dotcms-e2e-node/frontend/utils/api.ts | 117 +++ .../frontend/utils/contentUtils.ts | 740 ++++++++++-------- .../frontend/utils/dotCMSUtils.ts | 104 ++- e2e/dotcms-e2e-node/frontend/yarn.lock | 7 +- 18 files changed, 1421 insertions(+), 961 deletions(-) create mode 100644 e2e/dotcms-e2e-node/frontend/pages/listingContentTypes.page.ts create mode 100644 e2e/dotcms-e2e-node/frontend/pages/listngContent.page.ts create mode 100644 e2e/dotcms-e2e-node/frontend/pages/textField.page.ts create mode 100644 e2e/dotcms-e2e-node/frontend/utils/api.ts diff --git a/e2e/dotcms-e2e-node/frontend/index.js b/e2e/dotcms-e2e-node/frontend/index.js index 43d34115bc7d..75c414107b6d 100644 --- a/e2e/dotcms-e2e-node/frontend/index.js +++ b/e2e/dotcms-e2e-node/frontend/index.js @@ -1,55 +1,59 @@ -const fs = require('fs'); -const path = require('path'); -const xml2js = require('xml2js'); +const fs = require("fs"); +const path = require("path"); +const xml2js = require("xml2js"); const summaryResults = { - completed: 0, - errors: 0, - failures: 0, - skipped: 0 + completed: 0, + errors: 0, + failures: 0, + skipped: 0, }; // Default configurations -const defaultE2eTestsResultsDir = '../target/failsafe-reports'; // Default results directory +const defaultE2eTestsResultsDir = "../target/failsafe-reports"; // Default results directory // Function to generate failsafe-summary.xml const generateFailsafeSummaryXml = (e2eTestsResultsDir) => { - const builder = new xml2js.Builder(); - const result = summaryResults.failures > 0 ? 255 : 0; - const xmlObj = { - 'failsafe-summary': { - $: { result: result }, - completed: summaryResults.completed, - errors: summaryResults.errors, - failures: summaryResults.failures, - skipped: summaryResults.skipped - } - }; + const builder = new xml2js.Builder(); + const result = summaryResults.failures > 0 ? 255 : 0; + const xmlObj = { + "failsafe-summary": { + $: { result: result }, + completed: summaryResults.completed, + errors: summaryResults.errors, + failures: summaryResults.failures, + skipped: summaryResults.skipped, + }, + }; - if (summaryResults.failures > 0) { - xmlObj['failsafe-summary']['failureMessage'] = 'There are test failures.'; - } + if (summaryResults.failures > 0) { + xmlObj["failsafe-summary"]["failureMessage"] = "There are test failures."; + } - const xml = builder.buildObject(xmlObj); + const xml = builder.buildObject(xmlObj); - fs.writeFileSync(path.join(e2eTestsResultsDir, 'failsafe-summary.xml'), xml, 'utf8'); -} + fs.writeFileSync( + path.join(e2eTestsResultsDir, "failsafe-summary.xml"), + xml, + "utf8", + ); +}; const processTestsResults = async () => { - const parser = new xml2js.Parser(); - const xml = fs.readFileSync(process.env.PLAYWRIGHT_JUNIT_OUTPUT_FILE, 'utf8'); - const xmlDoc = await parser.parseStringPromise(xml); + const parser = new xml2js.Parser(); + const xml = fs.readFileSync(process.env.PLAYWRIGHT_JUNIT_OUTPUT_FILE, "utf8"); + const xmlDoc = await parser.parseStringPromise(xml); - xmlDoc.testsuites.testsuite.forEach((ts) => { - const tests = parseInt(ts.tests || '0'); - const errors = parseInt(ts.errors || '0'); - const failures = parseInt(ts.failures || '0'); - const skipped = parseInt(ts.skipped || '0'); - summaryResults.completed += tests; - summaryResults.errors += errors; - summaryResults.failures += failures; - summaryResults.skipped += skipped; - }); + xmlDoc.testsuites.testsuite.forEach((ts) => { + const tests = parseInt(ts.tests || "0"); + const errors = parseInt(ts.errors || "0"); + const failures = parseInt(ts.failures || "0"); + const skipped = parseInt(ts.skipped || "0"); + summaryResults.completed += tests; + summaryResults.errors += errors; + summaryResults.failures += failures; + summaryResults.skipped += skipped; + }); }; /** @@ -57,28 +61,28 @@ const processTestsResults = async () => { * Parses command line arguments for named parameters. */ async function main() { - const e2eTestsResultsDir = path.resolve(__dirname, defaultE2eTestsResultsDir); - // Ensure results directory exists - if (!fs.existsSync(e2eTestsResultsDir)) { - fs.mkdirSync(e2eTestsResultsDir, { recursive: true }); - } + const e2eTestsResultsDir = path.resolve(__dirname, defaultE2eTestsResultsDir); + // Ensure results directory exists + if (!fs.existsSync(e2eTestsResultsDir)) { + fs.mkdirSync(e2eTestsResultsDir, { recursive: true }); + } - try { - await processTestsResults(); - generateFailsafeSummaryXml(e2eTestsResultsDir); + try { + await processTestsResults(); + generateFailsafeSummaryXml(e2eTestsResultsDir); - if (summaryResults.failures > 0) { - console.error('Some E2E tests failed.'); - process.exit(0); - } - } catch (error) { - console.error('An error occurred:', error); - process.exit(1); + if (summaryResults.failures > 0) { + console.error("Some E2E tests failed."); + process.exit(0); } + } catch (error) { + console.error("An error occurred:", error); + process.exit(1); + } } // Run the main function and handle any errors main().catch((err) => { - console.error('Unhandled error:', err); - process.exit(1); + console.error("Unhandled error:", err); + process.exit(1); }); diff --git a/e2e/dotcms-e2e-node/frontend/locators/globalLocators.ts b/e2e/dotcms-e2e-node/frontend/locators/globalLocators.ts index 29fc3fb12bf5..d9e4a7aca4c0 100644 --- a/e2e/dotcms-e2e-node/frontend/locators/globalLocators.ts +++ b/e2e/dotcms-e2e-node/frontend/locators/globalLocators.ts @@ -2,48 +2,47 @@ * 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"]', +}; /** * 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" -} - -export { -} from './navigation/menuLocators'; + locator: "descriptionPage", + label: "Page", +}; +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 34bbedd8d8bc..067b4b5ae472 100644 --- a/e2e/dotcms-e2e-node/frontend/locators/navigation/menuLocators.ts +++ b/e2e/dotcms-e2e-node/frontend/locators/navigation/menuLocators.ts @@ -1,48 +1,49 @@ -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.locator('button[ng-reflect-ng-class="[object Object]"]').first(); 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 + this.EXPAND = page.getByRole("button", { name: "" }); + this.COLLAPSE = page + .locator('button[ng-reflect-ng-class="[object Object]"]') + .first(); + } +} diff --git a/e2e/dotcms-e2e-node/frontend/package.json b/e2e/dotcms-e2e-node/frontend/package.json index ec5bafc6d277..18a602449905 100644 --- a/e2e/dotcms-e2e-node/frontend/package.json +++ b/e2e/dotcms-e2e-node/frontend/package.json @@ -6,7 +6,8 @@ "devDependencies": { "@playwright/test": "^1.48.2", "@types/node": "^22.5.4", - "dotenv": "^16.4.5" + "dotenv": "^16.4.5", + "prettier": "3.4.2" }, "dependencies": { "jsdom": "^25.0.1", @@ -17,6 +18,7 @@ "start-local": "CURRENT_ENV=local yarn run start", "start-dev": "CURRENT_ENV=dev yarn run start", "start-ci": "CURRENT_ENV=ci yarn run start", - "post-testing": "PLAYWRIGHT_JUNIT_OUTPUT_FILE='../target/failsafe-reports/TEST-e2e-node-results.xml' node index.js" + "post-testing": "PLAYWRIGHT_JUNIT_OUTPUT_FILE='../target/failsafe-reports/TEST-e2e-node-results.xml' node index.js", + "format": "prettier --write ." } } diff --git a/e2e/dotcms-e2e-node/frontend/pages/listingContentTypes.page.ts b/e2e/dotcms-e2e-node/frontend/pages/listingContentTypes.page.ts new file mode 100644 index 000000000000..4e6c5b57606a --- /dev/null +++ b/e2e/dotcms-e2e-node/frontend/pages/listingContentTypes.page.ts @@ -0,0 +1,12 @@ +import { Page } from "@playwright/test"; + +export class ListingContentTypesPage { + + constructor(private page: Page) {} + + async goTo() { + await this.page.goto("/content-types-angular"); + } + + +} diff --git a/e2e/dotcms-e2e-node/frontend/pages/listngContent.page.ts b/e2e/dotcms-e2e-node/frontend/pages/listngContent.page.ts new file mode 100644 index 000000000000..2d7e1b80a832 --- /dev/null +++ b/e2e/dotcms-e2e-node/frontend/pages/listngContent.page.ts @@ -0,0 +1,26 @@ +import { Page } from "@playwright/test"; + +export class ListingContentPage { + + constructor(private page: Page) {} + #addBtn = this.page.locator("span[widgetid='dijit_form_DropDownButton_0']"); + #addNewContent = this.page.locator( + ".dijitPopup tr[aria-label='Add New Content']", + ); + + + async goTo(filter?: string) { + const urlPath = new URL("/c/content", this.page.url()); + + if (filter) { + urlPath.searchParams.set("filter", filter); + } + + await this.page.goto(urlPath.toString()); + } + + async clickAddNewContent() { + await this.#addBtn.click(); + await this.#addNewContent.click(); + } +} diff --git a/e2e/dotcms-e2e-node/frontend/pages/textField.page.ts b/e2e/dotcms-e2e-node/frontend/pages/textField.page.ts new file mode 100644 index 000000000000..af7755262b1b --- /dev/null +++ b/e2e/dotcms-e2e-node/frontend/pages/textField.page.ts @@ -0,0 +1,12 @@ +import { expect, Page } from "@playwright/test"; + +export class TextFieldPage { + constructor(private page: Page) {} + + async fill(vairableName: string, value: string) { + const input = this.page.locator(`input#${vairableName}`); + await input.fill(value); + + await expect(input).toHaveValue(value); + } +} diff --git a/e2e/dotcms-e2e-node/frontend/playwright.config.ts b/e2e/dotcms-e2e-node/frontend/playwright.config.ts index 9c8061750876..b2c6bc579fc5 100644 --- a/e2e/dotcms-e2e-node/frontend/playwright.config.ts +++ b/e2e/dotcms-e2e-node/frontend/playwright.config.ts @@ -1,38 +1,35 @@ -import * as dotenv from 'dotenv'; +import * as dotenv from "dotenv"; import * as path from "node:path"; -import { defineConfig, devices } from '@playwright/test'; -import {ReporterDescription} from "playwright/types/test"; +import { defineConfig, devices } from "@playwright/test"; +import { ReporterDescription } from "playwright/types/test"; const resolveEnvs = () => { - const envFiles = ['.env']; + const envFiles = [".env"]; - if (process.env.CURRENT_ENV === 'local') { - envFiles.push('.env.local'); - } else if (process.env.CURRENT_ENV === 'ci') { - envFiles.push('.env.ci'); - } else if (process.env.CURRENT_ENV === 'dev') { - envFiles.push('.env.dev'); + if (process.env.CURRENT_ENV === "local") { + envFiles.push(".env.local"); + } else if (process.env.CURRENT_ENV === "ci") { + envFiles.push(".env.ci"); + } else if (process.env.CURRENT_ENV === "dev") { + envFiles.push(".env.dev"); } envFiles.forEach((file) => { dotenv.config({ path: path.resolve(__dirname, file), - override: true + override: true, }); }); }; const resolveReporter = () => { - const reporter: ReporterDescription[] = [ - ['junit'], - ['github'] - ]; + const reporter: ReporterDescription[] = [["junit"], ["github"]]; if (!!process.env.INCLUDE_HTML) { - reporter.push(['html']) + reporter.push(["html"]); } return reporter; -} +}; resolveEnvs(); const reporter = resolveReporter(); @@ -41,7 +38,7 @@ const reporter = resolveReporter(); * See https://playwright.dev/docs/test-configuration. */ export default defineConfig({ - testDir: './tests', + testDir: "./tests", /* Run tests in files in parallel */ fullyParallel: true, /* Fail the build on CI if you accidentally left test.only in the source code. */ @@ -58,14 +55,14 @@ export default defineConfig({ use: { baseURL: process.env.BASE_URL, /* Collect trace when retrying the failed test. See https://playwright.dev/docs/trace-viewer */ - trace: 'on-first-retry', + trace: "on-first-retry", headless: !!process.env.HEADLESS, }, /* Configure projects for major browsers */ projects: [ { - name: 'chromium', - use: { ...devices['Desktop Chrome'] }, + name: "chromium", + use: { ...devices["Desktop Chrome"] }, }, /*{ @@ -79,9 +76,9 @@ export default defineConfig({ },*/ ], webServer: { - command: 'nx serve dotcms-ui', - cwd: '../../../core-web', - url: process.env.BASE_URL + '/dotAdmin', - reuseExistingServer: !!process.env.REUSE_SERVER - } + command: "nx serve dotcms-ui", + cwd: "../../../core-web", + url: process.env.BASE_URL + "/dotAdmin", + reuseExistingServer: !!process.env.REUSE_SERVER, + }, }); diff --git a/e2e/dotcms-e2e-node/frontend/tests/contentSearch/contentData.ts b/e2e/dotcms-e2e-node/frontend/tests/contentSearch/contentData.ts index 2d801d24918c..a80dde935ac0 100644 --- a/e2e/dotcms-e2e-node/frontend/tests/contentSearch/contentData.ts +++ b/e2e/dotcms-e2e-node/frontend/tests/contentSearch/contentData.ts @@ -1,46 +1,43 @@ -import {Page} from "@playwright/test"; +import { Page } from "@playwright/test"; /** * 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", + 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 d9df6e21bf93..017bdf35f432 100644 --- a/e2e/dotcms-e2e-node/frontend/tests/contentSearch/contentEditing.spec.ts +++ b/e2e/dotcms-e2e-node/frontend/tests/contentSearch/contentEditing.spec.ts @@ -1,92 +1,142 @@ -import {expect, test} from '@playwright/test'; -import {dotCMSUtils, waitForVisibleAndCallback} from '../../utils/dotCMSUtils'; +import { expect, test } from "@playwright/test"; 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"; - + 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"; /** * 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(), async () => {}); - - 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(), async () => {}); - 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 @@ -107,106 +157,149 @@ test('Validate required on blockContent fields', async ({page}) => { /** * 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); - - 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 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 to validate you are able to add file assets creating a new file */ -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 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 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'), async () => {}); - let 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, + 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, + 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 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, + 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"); }); /** @@ -228,83 +321,108 @@ test('Validate the title field is not changing in the file asset auto complete', /** * 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); - const iframe = page.frameLocator(iFramesLocators.main_iframe); - - 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("Add a new page", async ({ page }) => { + const contentUtils = new ContentUtils(page); + const iframe = page.frameLocator(iFramesLocators.main_iframe); + + 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'), 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("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/contentSearch/portletIntegrity.spec.ts b/e2e/dotcms-e2e-node/frontend/tests/contentSearch/portletIntegrity.spec.ts index 5b9a55722856..ed098be73cde 100644 --- a/e2e/dotcms-e2e-node/frontend/tests/contentSearch/portletIntegrity.spec.ts +++ b/e2e/dotcms-e2e-node/frontend/tests/contentSearch/portletIntegrity.spec.ts @@ -1,204 +1,263 @@ -import {expect, test} from '@playwright/test'; -import {dotCMSUtils, waitForVisibleAndCallback} from '../../utils/dotCMSUtils'; -import {ContentUtils} from '../../utils/contentUtils'; -import {addContent, iFramesLocators, contentGeneric} from '../../locators/globalLocators'; +import { expect, test } from "@playwright/test"; import { - GroupEntriesLocators, - MenuEntriesLocators, - ToolEntriesLocators -} from '../../locators/navigation/menuLocators'; -import {contentProperties, genericContent1} from './contentData'; + dotCMSUtils, + waitForVisibleAndCallback, +} from "../../utils/dotCMSUtils"; +import { ContentUtils } from "../../utils/contentUtils"; +import { + addContent, + iFramesLocators, + contentGeneric, +} from "../../locators/globalLocators"; +import { + GroupEntriesLocators, + MenuEntriesLocators, + ToolEntriesLocators, +} from "../../locators/navigation/menuLocators"; +import { contentProperties, genericContent1 } from "./contentData"; const cmsUtils = new dotCMSUtils(); /** * Test to navigate to the content portlet and login to the dotCMS instance * @param page - */ -test.beforeEach('Navigate to content portlet', async ({page}) => { - // Instance the menu Navigation locators - 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 }) => { + // Instance the menu Navigation locators + 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 validate the portlet title * @param page */ -test('Validate portlet title', async ({page}) => { - await expect(page.locator('p-breadcrumb')).toContainText('Search All'); +test("Validate portlet title", async ({ page }) => { + await expect(page.locator("p-breadcrumb")).toContainText("Search All"); }); - /** * Test to validate the filter is present and usable in the content portlet * @param page - */ -test('Search filter', 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'); - - // Validate the content has been created - await expect.soft(iframe.getByRole('link', {name: genericContent1.title}).first()).toBeVisible(); - await iframe.locator('#allFieldTB').fill(genericContent1.title); - await page.keyboard.press('Enter'); - - //validate the search filter is working - await expect(iframe.getByRole('link', {name: genericContent1.title}).first()).toBeVisible(); + */ +test("Search filter", 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"); + + // Validate the content has been created + await expect + .soft(iframe.getByRole("link", { name: genericContent1.title }).first()) + .toBeVisible(); + await iframe.locator("#allFieldTB").fill(genericContent1.title); + await page.keyboard.press("Enter"); + + //validate the search filter is working + await expect( + iframe.getByRole("link", { name: genericContent1.title }).first(), + ).toBeVisible(); }); /** * Test to validate the views are working in the content portlet * @param page - */ -test('Validate views buttons', async ({page}) => { - const iframe = page.frameLocator(iFramesLocators.main_iframe); - - await iframe.getByLabel('List', {exact: true}).isVisible(); - await iframe.getByLabel('Card').isVisible(); - - // Click on the card view - await iframe.getByLabel('Card').click(); - await iframe.locator('.hydrated > dot-contentlet-thumbnail > .hydrated').first().isVisible(); + */ +test("Validate views buttons", async ({ page }) => { + const iframe = page.frameLocator(iFramesLocators.main_iframe); + + await iframe.getByLabel("List", { exact: true }).isVisible(); + await iframe.getByLabel("Card").isVisible(); + + // Click on the card view + await iframe.getByLabel("Card").click(); + await iframe + .locator(".hydrated > dot-contentlet-thumbnail > .hydrated") + .first() + .isVisible(); }); /** * Test to validate the add content button is present and functional * @param page - */ -test('Validate add content button is present and functional', async ({page}) => { - const iframe = page.frameLocator(iFramesLocators.main_iframe); - - await iframe.locator(addContent.addBtn).click(); - await expect(iframe.getByLabel(addContent.addNewMenuLabel).getByText(addContent.addNewContentSubMenu)).toBeVisible(); + */ +test("Validate add content button is present and functional", async ({ + page, +}) => { + const iframe = page.frameLocator(iFramesLocators.main_iframe); + + await iframe.locator(addContent.addBtn).click(); + await expect( + iframe + .getByLabel(addContent.addNewMenuLabel) + .getByText(addContent.addNewContentSubMenu), + ).toBeVisible(); }); - /** * Test to validate the behavior of the bulk Workflow actions button * @param page */ -test('Validate bulk Workflow actions', async ({page}) => { - const iframe = page.frameLocator(iFramesLocators.main_iframe); - - // Validate the button is present and disabled by default - const workflowActionsButton = iframe.getByRole('button', {name: 'Available Workflow Actions'}); - await expect(workflowActionsButton).toBeDisabled(); - - // Check the checkbox and validate the button is enabled - await iframe.locator('#checkbox0').check(); - await expect(workflowActionsButton).toBeEnabled(); - - // Click on the button and validate the dialog is present - await workflowActionsButton.click(); - await expect(iframe.locator('.dijitDialog[role="dialog"]').first()).toBeVisible(); +test("Validate bulk Workflow actions", async ({ page }) => { + const iframe = page.frameLocator(iFramesLocators.main_iframe); + + // Validate the button is present and disabled by default + const workflowActionsButton = iframe.getByRole("button", { + name: "Available Workflow Actions", + }); + await expect(workflowActionsButton).toBeDisabled(); + + // Check the checkbox and validate the button is enabled + await iframe.locator("#checkbox0").check(); + await expect(workflowActionsButton).toBeEnabled(); + + // Click on the button and validate the dialog is present + await workflowActionsButton.click(); + await expect( + iframe.locator('.dijitDialog[role="dialog"]').first(), + ).toBeVisible(); }); - /** * Test to validate the search query is generating the correct results * @param page */ -test('Validate the search query', async ({page}) => { - const iframe = page.frameLocator(iFramesLocators.main_iframe); - await new ContentUtils(page).showQuery(iframe); - await expect(iframe.locator('#queryResults')).toBeVisible(); +test("Validate the search query", async ({ page }) => { + const iframe = page.frameLocator(iFramesLocators.main_iframe); + await new ContentUtils(page).showQuery(iframe); + await expect(iframe.locator("#queryResults")).toBeVisible(); }); /** * Test to validate the API button is working in the search query modal * @param page - */ -test('Validate the API link in search query modal is working', async ({page}) => { - const iframe = page.frameLocator(iFramesLocators.main_iframe); - await new ContentUtils(page).showQuery(iframe); - - // Wait for the new tab to open - const queryModal = page.waitForEvent('popup'); - await iframe.getByText('API', {exact: true}).click(); - const newPage = await queryModal; - - // Validate the new tab has opened - await newPage.waitForLoadState(); - expect(newPage.url()).toContain('about:blank'); - - //close the new tab - await newPage.close(); + */ +test("Validate the API link in search query modal is working", async ({ + page, +}) => { + const iframe = page.frameLocator(iFramesLocators.main_iframe); + await new ContentUtils(page).showQuery(iframe); + + // Wait for the new tab to open + const queryModal = page.waitForEvent("popup"); + await iframe.getByText("API", { exact: true }).click(); + const newPage = await queryModal; + + // Validate the new tab has opened + await newPage.waitForLoadState(); + expect(newPage.url()).toContain("about:blank"); + + //close the new tab + await newPage.close(); }); /** * Test to validate the clear button in the search filter * @param page - */ -test("Validate the clear button in the search filter", async ({page}) => { - const iframe = page.frameLocator(iFramesLocators.main_iframe); - - //expand the search filter - const advancedLinkLocator = iframe.getByRole('link', {name: 'Advanced'}); - await waitForVisibleAndCallback(advancedLinkLocator, () => advancedLinkLocator.click()); - - // Select the workflow in the search filter - await iframe.locator('#widget_scheme_id [data-dojo-attach-point="_buttonNode"]').click(); - await iframe.getByRole('option', {name: 'System Workflow'}).click(); - //TODO remove this - await page.waitForTimeout(1000); - - // Select the step in the search filter - const widgetStepIdLocator = iframe.locator('div[id=\'widget_step_id\'] div[data-dojo-attach-point=\'_buttonNode\']'); - await waitForVisibleAndCallback(widgetStepIdLocator, () => widgetStepIdLocator.click()); - const newOption = iframe.getByRole('option', {name: 'New'}); - await waitForVisibleAndCallback(newOption, () => newOption.click()); - - // Select the Show in the search filter - await iframe.locator('#widget_showingSelect [data-dojo-attach-point="_buttonNode"]').click(); - await iframe.getByRole('option', {name: 'Unpublished'}).click(); - - // Click on clear button - await iframe.getByLabel('Clear').click(); - - // Validate the search filter has been cleared - expect(await iframe.locator('input[name="scheme_id_select"]').getAttribute('value')).toBe('catchall'); - expect(await iframe.locator('input[name="step_id_select"]').getAttribute('value')).toBe('catchall'); - expect(await iframe.locator('#showingSelect').getAttribute('value')).toBe('All'); + */ +test("Validate the clear button in the search filter", async ({ page }) => { + const iframe = page.frameLocator(iFramesLocators.main_iframe); + + //expand the search filter + const advancedLinkLocator = iframe.getByRole("link", { name: "Advanced" }); + await waitForVisibleAndCallback(advancedLinkLocator, () => + advancedLinkLocator.click(), + ); + + // Select the workflow in the search filter + await iframe + .locator('#widget_scheme_id [data-dojo-attach-point="_buttonNode"]') + .click(); + await iframe.getByRole("option", { name: "System Workflow" }).click(); + //TODO remove this + await page.waitForTimeout(1000); + + // Select the step in the search filter + const widgetStepIdLocator = iframe.locator( + "div[id='widget_step_id'] div[data-dojo-attach-point='_buttonNode']", + ); + await waitForVisibleAndCallback(widgetStepIdLocator, () => + widgetStepIdLocator.click(), + ); + const newOption = iframe.getByRole("option", { name: "New" }); + await waitForVisibleAndCallback(newOption, () => newOption.click()); + + // Select the Show in the search filter + await iframe + .locator('#widget_showingSelect [data-dojo-attach-point="_buttonNode"]') + .click(); + await iframe.getByRole("option", { name: "Unpublished" }).click(); + + // Click on clear button + await iframe.getByLabel("Clear").click(); + + // Validate the search filter has been cleared + expect( + await iframe + .locator('input[name="scheme_id_select"]') + .getAttribute("value"), + ).toBe("catchall"); + expect( + await iframe.locator('input[name="step_id_select"]').getAttribute("value"), + ).toBe("catchall"); + expect(await iframe.locator("#showingSelect").getAttribute("value")).toBe( + "All", + ); }); /** * Test to validate the hide button in the search filter * @param page */ -test('Validate the hide button collapse the filter', async ({page}) => { - const iframe = page.frameLocator(iFramesLocators.main_iframe); - - await expect(iframe.getByRole('button', {name: 'Search'})).toBeVisible(); - let advancedLinkLocator = iframe.getByRole('link', {name: 'Advanced'}); - await waitForVisibleAndCallback(advancedLinkLocator, () => advancedLinkLocator.click()); - - await page.waitForTimeout(1000); - await expect(iframe.getByRole('link', {name: 'Advanced'})).not.toBeVisible(); - - // Click on the hide button - await iframe.getByRole('link', {name: 'Hide'}).click(); - await page.waitForTimeout(1000); - - // Validate the filter has been collapsed - await expect(iframe.getByRole('link', {name: 'Advanced'})).toBeVisible(); +test("Validate the hide button collapse the filter", async ({ page }) => { + const iframe = page.frameLocator(iFramesLocators.main_iframe); + + await expect(iframe.getByRole("button", { name: "Search" })).toBeVisible(); + let advancedLinkLocator = iframe.getByRole("link", { name: "Advanced" }); + await waitForVisibleAndCallback(advancedLinkLocator, () => + advancedLinkLocator.click(), + ); + + await page.waitForTimeout(1000); + await expect( + iframe.getByRole("link", { name: "Advanced" }), + ).not.toBeVisible(); + + // Click on the hide button + await iframe.getByRole("link", { name: "Hide" }).click(); + await page.waitForTimeout(1000); + + // Validate the filter has been collapsed + await expect(iframe.getByRole("link", { name: "Advanced" })).toBeVisible(); }); diff --git a/e2e/dotcms-e2e-node/frontend/tests/login/credentialsData.ts b/e2e/dotcms-e2e-node/frontend/tests/login/credentialsData.ts index 8f46ed4bc30a..444ffe912a36 100644 --- a/e2e/dotcms-e2e-node/frontend/tests/login/credentialsData.ts +++ b/e2e/dotcms-e2e-node/frontend/tests/login/credentialsData.ts @@ -2,35 +2,30 @@ * This is a valid user that can do everything */ export const admin1 = { - username: "admin@dotcms.com", - password: "admin" -} + username: "admin@dotcms.com", + password: "admin", +}; /** * This is a limited user that can only view the content */ export const limited1 = { - username: "chris@dotcms.com", - password: "chris" -} + username: "chris@dotcms.com", + password: "chris", +}; /** * Combination of a valid user and wrong password */ export const wrong1 = { - username: "admin@dotcms.com", - password: "password" -} + username: "admin@dotcms.com", + password: "password", +}; /** * Combination of a wrong user and valid password */ export const wrong2 = { - username: "chris2@dotcms.com", - password: "chris" -} - - - - - + username: "chris2@dotcms.com", + password: "chris", +}; 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 fc0d2b6c56e2..c6df4de8898d 100644 --- a/e2e/dotcms-e2e-node/frontend/tests/login/login.spec.ts +++ b/e2e/dotcms-e2e-node/frontend/tests/login/login.spec.ts @@ -1,47 +1,49 @@ -import {test, expect} from '@playwright/test'; -import {admin1, limited1, wrong1, wrong2} from './credentialsData'; - +import { test, expect } from "@playwright/test"; +import { admin1, limited1, 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}); - }); -}); \ No newline at end of file + // Assertion and further test steps + await expect(page.getByTestId("message")).toBeVisible({ timeout: 30000 }); + }); +}); diff --git a/e2e/dotcms-e2e-node/frontend/tests/login/translations.spec.ts b/e2e/dotcms-e2e-node/frontend/tests/login/translations.spec.ts index bdccbf35de41..76317ceb5b95 100644 --- a/e2e/dotcms-e2e-node/frontend/tests/login/translations.spec.ts +++ b/e2e/dotcms-e2e-node/frontend/tests/login/translations.spec.ts @@ -1,33 +1,37 @@ -import {expect, test} from '@playwright/test'; -import {assert} from 'console'; -import {waitForVisibleAndCallback} from "../../utils/dotCMSUtils"; +import { expect, test } from "@playwright/test"; +import { assert } from "console"; +import { waitForVisibleAndCallback } from "../../utils/dotCMSUtils"; const languages = [ - {language: 'español (España)', translation: '¡Bienvenido!'}, - {language: 'italiano (Italia)', translation: 'Benvenuto!'}, - {language: 'français (France)', translation: 'Bienvenue !'}, - {language: 'Deutsch (Deutschland)', translation: 'Willkommen!'}, - {language: '中文 (中国)', translation: '欢迎'}, - {language: 'Nederlands (Nederland)', translation: 'Welkom!'}, - {language: 'русский (Россия)', translation: 'Добро пожаловать!'}, - {language: 'suomi (Suomi)', translation: 'Tervetuloa!'} + { language: "español (España)", translation: "¡Bienvenido!" }, + { language: "italiano (Italia)", translation: "Benvenuto!" }, + { language: "français (France)", translation: "Bienvenue !" }, + { language: "Deutsch (Deutschland)", translation: "Willkommen!" }, + { language: "中文 (中国)", translation: "欢迎" }, + { language: "Nederlands (Nederland)", translation: "Welkom!" }, + { language: "русский (Россия)", translation: "Добро пожаловать!" }, + { language: "suomi (Suomi)", translation: "Tervetuloa!" }, ]; /** * Test to validate the translations of the login page */ -languages.forEach(list => { - test(`Validate Translation: ${list.language}`, async ({page}) => { - const {language, translation} = list; +languages.forEach((list) => { + test(`Validate Translation: ${list.language}`, async ({ page }) => { + const { language, translation } = list; - await page.goto('/dotAdmin'); - const dropdownTriggerLocator = page.getByLabel('dropdown trigger'); - await waitForVisibleAndCallback(dropdownTriggerLocator, () => dropdownTriggerLocator.click()); + await page.goto("/dotAdmin"); + const dropdownTriggerLocator = page.getByLabel("dropdown trigger"); + await waitForVisibleAndCallback(dropdownTriggerLocator, () => + dropdownTriggerLocator.click(), + ); - const pageByTextLocator = page.getByText(language); - await waitForVisibleAndCallback(pageByTextLocator, () => pageByTextLocator.click()); + const pageByTextLocator = page.getByText(language); + await waitForVisibleAndCallback(pageByTextLocator, () => + pageByTextLocator.click(), + ); - // Assertion of the translation - assert(await expect(page.getByTestId('header')).toContainText(translation)); - }); + // Assertion of the translation + assert(await expect(page.getByTestId("header")).toContainText(translation)); + }); }); diff --git a/e2e/dotcms-e2e-node/frontend/utils/api.ts b/e2e/dotcms-e2e-node/frontend/utils/api.ts new file mode 100644 index 000000000000..a0e79d472089 --- /dev/null +++ b/e2e/dotcms-e2e-node/frontend/utils/api.ts @@ -0,0 +1,117 @@ +import { APIRequestContext, expect } from "@playwright/test"; + +export async function createBasicContentType(request: APIRequestContext) { + const data = { + defaultType: false, + icon: "new_releases", + fixed: false, + system: false, + clazz: "com.dotcms.contenttype.model.type.ImmutableSimpleContentType", + description: "Include all fields", + host: "48190c8c-42c4-46af-8d1a-0cd5db894797", + folder: "SYSTEM_FOLDER", + name: "BasicContentType", + systemActionMappings: { + NEW: "", + }, + metadata: { + CONTENT_EDITOR2_ENABLED: true, + }, + workflow: ["d61a59e1-a49c-46f2-a929-db2b4bfa88b2"], + }; + + const endpoint = `/api/v1/contenttype`; + + const contentTypeResponse = await request.post(endpoint, { + data, + }); + + expect(contentTypeResponse.status()).toBe(201); + + const contentTypeResponseBody = await contentTypeResponse.json(); + expect(contentTypeResponseBody.id).toBeDefined(); + + return contentTypeResponseBody; +} + +export async function addTextFieldToContentType( + request: APIRequestContext, + contentTypeId: string, +) { + const data = { + layout: [ + { + divider: { + clazz: "com.dotcms.contenttype.model.field.ImmutableRowField", + contentTypeId, + dataType: "SYSTEM", + fieldContentTypeProperties: [], + fieldType: "Row", + fieldTypeLabel: "Row", + fieldVariables: [], + fixed: false, + forceIncludeInApi: false, + iDate: 1735844752000, + indexed: false, + listed: false, + modDate: 1735844752000, + name: "Row Field", + readOnly: false, + required: false, + searchable: false, + sortOrder: -1, + unique: false, + }, + columns: [ + { + columnDivider: { + clazz: "com.dotcms.contenttype.model.field.ImmutableColumnField", + contentTypeId, + dataType: "SYSTEM", + fieldContentTypeProperties: [], + fieldType: "Column", + fieldTypeLabel: "Column", + fieldVariables: [], + fixed: false, + forceIncludeInApi: false, + iDate: 1735844752000, + indexed: false, + listed: false, + modDate: 1735844752000, + name: "Column Field", + readOnly: false, + required: false, + searchable: false, + sortOrder: -1, + unique: false, + }, + fields: [ + { + clazz: "com.dotcms.contenttype.model.field.ImmutableTextField", + name: "Text Field", + dataType: "TEXT", + regexCheck: "", + defaultValue: "", + hint: "Text hint", + required: true, + searchable: false, + indexed: false, + listed: false, + unique: false, + id: null, + }, + ], + }, + ], + }, + ], + }; + + const endpoint = `/api/v3/contenttype/${contentTypeId}/fields/move`; + + const response = await request.put(endpoint, { + data, + }); + + expect(response.status()).toBe(200); +} diff --git a/e2e/dotcms-e2e-node/frontend/utils/contentUtils.ts b/e2e/dotcms-e2e-node/frontend/utils/contentUtils.ts index 1cbd07830e66..bec7891bd082 100644 --- a/e2e/dotcms-e2e-node/frontend/utils/contentUtils.ts +++ b/e2e/dotcms-e2e-node/frontend/utils/contentUtils.ts @@ -1,356 +1,446 @@ -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 } 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); - - //await dotIframe.locator(iFramesLocators.wysiwygFrame).contentFrame().locator('#tinymce').fill(body); - //Click on action - await dotIframe.getByText(action).first().click(); + 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); + + //await dotIframe.locator(iFramesLocators.wysiwygFrame).contentFrame().locator('#tinymce').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" }), + async () => {}, + ); } - /** - * 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 waitForVisibleAndCallback(dotIframe.locator("#title"), () => + dotIframe.locator("#title").fill(title), + ); - 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' }), async () => {}); - } - - await waitForVisibleAndCallback(dotIframe.locator('#title'), () => - 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); + + 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(), + ); + } + + /** + * 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 iframe.locator("#dijit_form_DropDownButton_0").click(); + await expect(iframe.getByLabel("actionPrimaryMenu")).toBeVisible(); + 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)"), + async () => {}, + ); + 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 second row of the table.`, ); - - if (action) { - await dotIframe.getByText(action).first().click(); - } + 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); - - 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()); + console.log( + `The text "${text}" does not exist in the second row of the 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 iframe.locator('#dijit_form_DropDownButton_0').click(); - await expect(iframe.getByLabel('actionPrimaryMenu')).toBeVisible(); - 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()); + 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 iframe = page.frameLocator(iFramesLocators.main_iframe); + 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(); - } - - /** - * 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 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; + } - 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 second row of the table.`); - return true; - } - } - - console.log(`The text "${text}" does not exist in the second row of the 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(); - } - } - 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 iframe = page.frameLocator(iFramesLocators.main_iframe); - 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'); + await page.waitForLoadState(); } - - /** - * 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(); - } + } + + /** + * 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", + }); } - - /** - * 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()); + 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(), + ); + } + + /** + * 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"); + } } - /** - * 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; + 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(); } - - - /** - * 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 (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(); + } } - /** * Parameters for the fillFileAssetForm method */ interface FileAssetFormParams { - page: Page; - host: string; - title: string; - action?: string; - fileName?: string; - fromURL?: string; - newFileName?: string; - newFileText?: string; + page: Page; + host: string; + title: string; + action?: string; + fileName?: string; + fromURL?: string; + newFileName?: string; + newFileText?: string; } /** * Parameters for the fillPageAssetForm method */ interface PageAssetFormParams { - page: Page; - title: string; - url?: string; - host?: string; - action?: string; - template?: string; - friendlyName?: string; - showOnMenu?: boolean; - sortOrder?: string; - cacheTTL?: number + page: Page; + title: string; + url?: string; + host?: string; + action?: 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 77f4e0035785..a9a196c11fbb 100644 --- a/e2e/dotcms-e2e-node/frontend/utils/dotCMSUtils.ts +++ b/e2e/dotcms-e2e-node/frontend/utils/dotCMSUtils.ts @@ -1,57 +1,74 @@ -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; + 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(); + } +} /** * 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}); -} +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(); +export const waitForAndCallback = async ( + locator: Locator, + state: "attached" | "detached" | "visible" | "hidden", + callback: () => Promise, +): Promise => { + await waitFor(locator, state); + await callback(); }; /** @@ -59,6 +76,9 @@ export const waitForAndCallback = async (locator: Locator, state: "attached" | " * @param locator * @param callback */ -export const waitForVisibleAndCallback = async (locator: Locator, callback: () => Promise): Promise => { - await waitForAndCallback(locator, 'visible', callback); -}; \ No newline at end of file +export const waitForVisibleAndCallback = async ( + locator: Locator, + callback: () => Promise, +): Promise => { + await waitForAndCallback(locator, "visible", callback); +}; diff --git a/e2e/dotcms-e2e-node/frontend/yarn.lock b/e2e/dotcms-e2e-node/frontend/yarn.lock index b24babe5f978..d2cbdc2bd92a 100644 --- a/e2e/dotcms-e2e-node/frontend/yarn.lock +++ b/e2e/dotcms-e2e-node/frontend/yarn.lock @@ -50,7 +50,7 @@ data-urls@^5.0.0: whatwg-mimetype "^4.0.0" whatwg-url "^14.0.0" -debug@^4.3.4, debug@4: +debug@4, debug@^4.3.4: version "4.3.7" resolved "https://registry.npmjs.org/debug/-/debug-4.3.7.tgz" integrity sha512-Er2nc/H7RrMXZBFCEim6TCmMk02Z8vLC2Rbi1KEBggpo0fS6l0S1nnapwmIi3yW/+GOJap1Krg4w0Hg80oCqgQ== @@ -196,6 +196,11 @@ playwright@1.48.2: optionalDependencies: fsevents "2.3.2" +prettier@3.4.2: + version "3.4.2" + resolved "https://registry.npmjs.org/prettier/-/prettier-3.4.2.tgz#a5ce1fb522a588bf2b78ca44c6e6fe5aa5a2b13f" + integrity sha512-e9MewbtFo+Fevyuxn/4rrcDAaq0IYxPGLvObpQjiZBMAzB9IGmzlnG9RZy3FFas+eBMu2vA0CszMeduow5dIuQ== + punycode@^2.3.1: version "2.3.1" resolved "https://registry.npmjs.org/punycode/-/punycode-2.3.1.tgz" From c5d9b2c2e03228ec182905e3f8de49546f412e18 Mon Sep 17 00:00:00 2001 From: Nicolas Molina Date: Thu, 2 Jan 2025 17:15:29 -0400 Subject: [PATCH 02/25] e2e: apply format --- .../frontend/pages/listingContentTypes.page.ts | 3 --- .../frontend/pages/listngContent.page.ts | 6 ++---- e2e/dotcms-e2e-node/frontend/pages/textField.page.ts | 12 ++++++------ 3 files changed, 8 insertions(+), 13 deletions(-) diff --git a/e2e/dotcms-e2e-node/frontend/pages/listingContentTypes.page.ts b/e2e/dotcms-e2e-node/frontend/pages/listingContentTypes.page.ts index 4e6c5b57606a..5b669c08c1fe 100644 --- a/e2e/dotcms-e2e-node/frontend/pages/listingContentTypes.page.ts +++ b/e2e/dotcms-e2e-node/frontend/pages/listingContentTypes.page.ts @@ -1,12 +1,9 @@ import { Page } from "@playwright/test"; export class ListingContentTypesPage { - constructor(private page: Page) {} async goTo() { await this.page.goto("/content-types-angular"); } - - } diff --git a/e2e/dotcms-e2e-node/frontend/pages/listngContent.page.ts b/e2e/dotcms-e2e-node/frontend/pages/listngContent.page.ts index 2d7e1b80a832..74abbd30b503 100644 --- a/e2e/dotcms-e2e-node/frontend/pages/listngContent.page.ts +++ b/e2e/dotcms-e2e-node/frontend/pages/listngContent.page.ts @@ -1,14 +1,12 @@ import { Page } from "@playwright/test"; export class ListingContentPage { - - constructor(private page: Page) {} - #addBtn = this.page.locator("span[widgetid='dijit_form_DropDownButton_0']"); + constructor(private page: Page) {} + #addBtn = this.page.locator("span[widgetid='dijit_form_DropDownButton_0']"); #addNewContent = this.page.locator( ".dijitPopup tr[aria-label='Add New Content']", ); - async goTo(filter?: string) { const urlPath = new URL("/c/content", this.page.url()); diff --git a/e2e/dotcms-e2e-node/frontend/pages/textField.page.ts b/e2e/dotcms-e2e-node/frontend/pages/textField.page.ts index af7755262b1b..b18ea197208c 100644 --- a/e2e/dotcms-e2e-node/frontend/pages/textField.page.ts +++ b/e2e/dotcms-e2e-node/frontend/pages/textField.page.ts @@ -1,12 +1,12 @@ import { expect, Page } from "@playwright/test"; export class TextFieldPage { - constructor(private page: Page) {} + constructor(private page: Page) {} - async fill(vairableName: string, value: string) { - const input = this.page.locator(`input#${vairableName}`); - await input.fill(value); + async fill(vairableName: string, value: string) { + const input = this.page.locator(`input#${vairableName}`); + await input.fill(value); - await expect(input).toHaveValue(value); - } + await expect(input).toHaveValue(value); + } } From e353da08c582367e003dfe122eef0689ada6ba51 Mon Sep 17 00:00:00 2001 From: Nicolas Molina Date: Mon, 13 Jan 2025 11:26:11 -0400 Subject: [PATCH 03/25] chore(e2e): sync with master --- e2e/dotcms-e2e-node/frontend/package.json | 9 --------- 1 file changed, 9 deletions(-) diff --git a/e2e/dotcms-e2e-node/frontend/package.json b/e2e/dotcms-e2e-node/frontend/package.json index c3476b07202a..a4a702750148 100644 --- a/e2e/dotcms-e2e-node/frontend/package.json +++ b/e2e/dotcms-e2e-node/frontend/package.json @@ -7,10 +7,6 @@ "@eslint/js": "^9.17.0", "@playwright/test": "^1.48.2", "@types/node": "^22.5.4", -<<<<<<< HEAD - "dotenv": "^16.4.5", - "prettier": "3.4.2" -======= "@typescript-eslint/eslint-plugin": "^8.19.0", "dotenv": "^16.4.5", "eslint": "^9.17.0", @@ -19,7 +15,6 @@ "prettier": "3.4.2", "typescript": "^5.7.2", "typescript-eslint": "^8.19.0" ->>>>>>> main }, "dependencies": { "jsdom": "^25.0.1", @@ -32,12 +27,8 @@ "start-dev": "CURRENT_ENV=dev yarn run start", "start-ci": "CURRENT_ENV=ci yarn run start", "post-testing": "PLAYWRIGHT_JUNIT_OUTPUT_FILE='../target/failsafe-reports/TEST-e2e-node-results.xml' node index.js", -<<<<<<< HEAD - "format": "prettier --write ." -======= "format": "prettier --write .", "lint": "eslint .", "lint:fix": "eslint . --fix" ->>>>>>> main } } From 640af1a860bc2b4dd7a9243ca204cf6685505298 Mon Sep 17 00:00:00 2001 From: Nicolas Molina Date: Mon, 13 Jan 2025 11:26:37 -0400 Subject: [PATCH 04/25] chore(e2e): sync with master --- e2e/dotcms-e2e-node/frontend/playwright.config.ts | 4 ---- 1 file changed, 4 deletions(-) diff --git a/e2e/dotcms-e2e-node/frontend/playwright.config.ts b/e2e/dotcms-e2e-node/frontend/playwright.config.ts index 53453b991306..0faf66b1cf99 100644 --- a/e2e/dotcms-e2e-node/frontend/playwright.config.ts +++ b/e2e/dotcms-e2e-node/frontend/playwright.config.ts @@ -24,11 +24,7 @@ const resolveEnvs = () => { const resolveReporter = () => { const reporter: ReporterDescription[] = [["junit"], ["github"]]; -<<<<<<< HEAD - if (!!process.env.INCLUDE_HTML) { -======= if (!process.env.INCLUDE_HTML) { ->>>>>>> main reporter.push(["html"]); } From 61744c974b447b32679218adc0aa087d14824fc7 Mon Sep 17 00:00:00 2001 From: Nicolas Molina Date: Mon, 13 Jan 2025 11:27:29 -0400 Subject: [PATCH 05/25] chore(e2e): sync with master --- .../frontend/utils/dotCMSUtils.ts | 18 ------------------ 1 file changed, 18 deletions(-) diff --git a/e2e/dotcms-e2e-node/frontend/utils/dotCMSUtils.ts b/e2e/dotcms-e2e-node/frontend/utils/dotCMSUtils.ts index 7515ef87c146..7d84752eb5de 100644 --- a/e2e/dotcms-e2e-node/frontend/utils/dotCMSUtils.ts +++ b/e2e/dotcms-e2e-node/frontend/utils/dotCMSUtils.ts @@ -2,10 +2,6 @@ import { Page, expect, Locator } from "@playwright/test"; import { loginLocators } from "../locators/globalLocators"; export class dotCMSUtils { -<<<<<<< HEAD - page: Page; -======= ->>>>>>> main /** * Login to dotCMS @@ -36,19 +32,6 @@ export class dotCMSUtils { ); } -<<<<<<< HEAD - /** - * 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 @@ -60,7 +43,6 @@ export class dotCMSUtils { await group.click(); await tool.click(); } ->>>>>>> main } /** From 56e0ac8d8f41ce719dff35b1b52b7ae9639d368c Mon Sep 17 00:00:00 2001 From: Nicolas Molina Date: Mon, 13 Jan 2025 11:28:27 -0400 Subject: [PATCH 06/25] chore(e2e): sync with master --- .../frontend/utils/dotCMSUtils.ts | 78 +++++++------------ 1 file changed, 29 insertions(+), 49 deletions(-) diff --git a/e2e/dotcms-e2e-node/frontend/utils/dotCMSUtils.ts b/e2e/dotcms-e2e-node/frontend/utils/dotCMSUtils.ts index 7d84752eb5de..2ab35d19b792 100644 --- a/e2e/dotcms-e2e-node/frontend/utils/dotCMSUtils.ts +++ b/e2e/dotcms-e2e-node/frontend/utils/dotCMSUtils.ts @@ -1,36 +1,26 @@ -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 { - /** - * 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 @@ -50,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 @@ -63,13 +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); - await callback(); +export const waitForAndCallback = async (locator: Locator, state: "attached" | "detached" | "visible" | "hidden", callback: () => Promise): Promise => { + await waitFor(locator, state); + await callback(); }; /** @@ -77,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 01b89ce8d6f62a8d1e87adf3e64254510a7dbfe7 Mon Sep 17 00:00:00 2001 From: Nicolas Molina Date: Mon, 13 Jan 2025 11:29:12 -0400 Subject: [PATCH 07/25] chore(e2e): sync with master --- e2e/dotcms-e2e-node/frontend/yarn.lock | 7 ------- 1 file changed, 7 deletions(-) diff --git a/e2e/dotcms-e2e-node/frontend/yarn.lock b/e2e/dotcms-e2e-node/frontend/yarn.lock index 636aad8dae36..48c37d48d78b 100644 --- a/e2e/dotcms-e2e-node/frontend/yarn.lock +++ b/e2e/dotcms-e2e-node/frontend/yarn.lock @@ -887,24 +887,17 @@ playwright@1.48.2: optionalDependencies: fsevents "2.3.2" -<<<<<<< HEAD -======= prelude-ls@^1.2.1: version "1.2.1" resolved "https://registry.npmjs.org/prelude-ls/-/prelude-ls-1.2.1.tgz#debc6489d7a6e6b0e7611888cec880337d316396" integrity sha512-vkcDPrRZo1QZLbn5RLGPpg/WmIQ65qoWWhcGKf/b5eplkkarX0m9z8ppCat4mlOqUsWpyNuYgO3VRyrYHSzX5g== ->>>>>>> main prettier@3.4.2: version "3.4.2" resolved "https://registry.npmjs.org/prettier/-/prettier-3.4.2.tgz#a5ce1fb522a588bf2b78ca44c6e6fe5aa5a2b13f" integrity sha512-e9MewbtFo+Fevyuxn/4rrcDAaq0IYxPGLvObpQjiZBMAzB9IGmzlnG9RZy3FFas+eBMu2vA0CszMeduow5dIuQ== -<<<<<<< HEAD -punycode@^2.3.1: -======= punycode@^2.1.0, punycode@^2.3.1: ->>>>>>> main version "2.3.1" resolved "https://registry.npmjs.org/punycode/-/punycode-2.3.1.tgz" integrity sha512-vYt7UD1U9Wg6138shLtLOvdAu+8DsC/ilFtEVHcH+wydcSpNE20AfSOduf6MkRFahL5FY7X1oU7nKVZFtfq8Fg== From d5790a4c62c5eb2a8960ad5d6cb0fe21d9ced6f0 Mon Sep 17 00:00:00 2001 From: Nicolas Molina Date: Mon, 13 Jan 2025 11:30:11 -0400 Subject: [PATCH 08/25] chore(e2e): sync with master --- .../locators/navigation/menuLocators.ts | 48 +++++++++---------- 1 file changed, 24 insertions(+), 24 deletions(-) diff --git a/e2e/dotcms-e2e-node/frontend/locators/navigation/menuLocators.ts b/e2e/dotcms-e2e-node/frontend/locators/navigation/menuLocators.ts index cc6704cf1da1..cda521ff3009 100644 --- a/e2e/dotcms-e2e-node/frontend/locators/navigation/menuLocators.ts +++ b/e2e/dotcms-e2e-node/frontend/locators/navigation/menuLocators.ts @@ -1,44 +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.getByRole('button', { name: '' }); this.COLLAPSE = page.locator('button[ng-reflect-ng-class="[object Object]"]').first(); } -} +} \ No newline at end of file From 5771ebaa67cfd9dad48c3330c8220b45728dd0c1 Mon Sep 17 00:00:00 2001 From: Nicolas Molina Date: Mon, 13 Jan 2025 11:31:24 -0400 Subject: [PATCH 09/25] chore(e2e): sync with master --- .../contentSearch/contentEditing.spec.ts | 195 ++++++------------ 1 file changed, 66 insertions(+), 129 deletions(-) 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 89209c0ca74c..caa167dbdb80 100644 --- a/e2e/dotcms-e2e-node/frontend/tests/contentSearch/contentEditing.spec.ts +++ b/e2e/dotcms-e2e-node/frontend/tests/contentSearch/contentEditing.spec.ts @@ -1,27 +1,15 @@ -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 @@ -34,81 +22,51 @@ test.beforeEach('Navigate to content portlet', async ({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"), - ); + // 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(), - async () => {}, - ); - - 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(), - async () => {}, - ); - 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 */ @@ -120,23 +78,14 @@ test('Delete a generic 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.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 @@ -176,10 +125,8 @@ test('Validate adding file assets from URL', async ({page}) => { /** * Test to validate you are able to add file assets creating a new file */ -test("Validate you are able to add file assets creating a new file", async ({ - page, -}) => { - const contentUtils = new ContentUtils(page); +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); await contentUtils.fillFileAssetForm({ @@ -402,9 +349,9 @@ test('Add a new page', async ({page}) => { /** * 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); +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); await contentUtils.fillPageAssetForm({ @@ -417,23 +364,17 @@ test("Validate required fields on page asset", async ({ page }) => { }); 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(); + 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); +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); await contentUtils.fillPageAssetForm({ @@ -444,12 +385,8 @@ test("Validate auto generation of fields on page asset", async ({ page }) => { showOnMenu: pageAssetContent.showOnMenu }); - await expect(detailFrame.locator("#url")).toHaveValue( - pageAssetContent.title.toLowerCase(), - ); - await expect(detailFrame.locator("#friendlyName")).toHaveValue( - pageAssetContent.title, - ); + await expect(detailFrame.locator('#url')).toHaveValue(pageAssetContent.title.toLowerCase()); + await expect(detailFrame.locator('#friendlyName')).toHaveValue(pageAssetContent.title); }); /** @@ -469,4 +406,4 @@ test('Validate you are able to delete pages', async ({page}) => { const contentUtils = new ContentUtils(page); await contentUtils.selectTypeOnFilter(page, pageAsset.locator); await contentUtils.deleteContent(page, pageAssetContent.title); -}); +}); \ No newline at end of file From d54c05bdf5112ec24c8711e5d47808696b703b3d Mon Sep 17 00:00:00 2001 From: Nicolas Molina Date: Mon, 13 Jan 2025 11:32:14 -0400 Subject: [PATCH 10/25] chore(e2e): sync with master --- .../frontend/tests/login/login.spec.ts | 55 +++++++++---------- 1 file changed, 26 insertions(+), 29 deletions(-) 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 4f16e20d75b8..3b7a07021d81 100644 --- a/e2e/dotcms-e2e-node/frontend/tests/login/login.spec.ts +++ b/e2e/dotcms-e2e-node/frontend/tests/login/login.spec.ts @@ -3,48 +3,45 @@ 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 From 496f8376978dcf6db1b2e9c17f57cf7968fcc93d Mon Sep 17 00:00:00 2001 From: Nicolas Molina Date: Mon, 13 Jan 2025 11:33:32 -0400 Subject: [PATCH 11/25] chore(e2e): sync with master --- .../frontend/locators/globalLocators.ts | 13 +- .../locators/navigation/menuLocators.ts | 61 +- .../tests/contentSearch/contentData.ts | 18 +- .../contentSearch/contentEditing.spec.ts | 727 ++++++++++------ .../contentSearch/portletIntegrity.spec.ts | 18 +- .../frontend/tests/login/login.spec.ts | 60 +- .../frontend/utils/contentUtils.ts | 818 ++++++++++-------- .../frontend/utils/dotCMSUtils.ts | 101 ++- 8 files changed, 1059 insertions(+), 757 deletions(-) diff --git a/e2e/dotcms-e2e-node/frontend/locators/globalLocators.ts b/e2e/dotcms-e2e-node/frontend/locators/globalLocators.ts index 12fe5729746d..1aa3fb4c485b 100644 --- a/e2e/dotcms-e2e-node/frontend/locators/globalLocators.ts +++ b/e2e/dotcms-e2e-node/frontend/locators/globalLocators.ts @@ -2,12 +2,13 @@ * 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"]', - dot_edit_iframe: 'dot-edit-contentlet iframe[name="detailFrame"]', -} + 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. diff --git a/e2e/dotcms-e2e-node/frontend/locators/navigation/menuLocators.ts b/e2e/dotcms-e2e-node/frontend/locators/navigation/menuLocators.ts index cda521ff3009..58351841889f 100644 --- a/e2e/dotcms-e2e-node/frontend/locators/navigation/menuLocators.ts +++ b/e2e/dotcms-e2e-node/frontend/locators/navigation/menuLocators.ts @@ -1,44 +1,45 @@ -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; - - constructor(page: Page) { - this.EXPAND = page.getByRole('button', { name: '' }); - this.COLLAPSE = page.locator('button[ng-reflect-ng-class="[object Object]"]').first(); - - } -} \ No newline at end of file + readonly EXPAND: Locator; + readonly COLLAPSE: Locator; + + constructor(page: Page) { + this.EXPAND = page.getByRole("button", { name: "" }); + this.COLLAPSE = page + .locator('button[ng-reflect-ng-class="[object Object]"]') + .first(); + } +} diff --git a/e2e/dotcms-e2e-node/frontend/tests/contentSearch/contentData.ts b/e2e/dotcms-e2e-node/frontend/tests/contentSearch/contentData.ts index 48a3facd4cfa..b41f851e054f 100644 --- a/e2e/dotcms-e2e-node/frontend/tests/contentSearch/contentData.ts +++ b/e2e/dotcms-e2e-node/frontend/tests/contentSearch/contentData.ts @@ -1,4 +1,3 @@ - /** * Content to add a Rich Text content */ @@ -22,14 +21,15 @@ export const contentProperties = { }; 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", - newFileTextEdited:"Validate you are able to edit text on binary fields", - 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", 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 caa167dbdb80..baa5dd7a280f 100644 --- a/e2e/dotcms-e2e-node/frontend/tests/contentSearch/contentEditing.spec.ts +++ b/e2e/dotcms-e2e-node/frontend/tests/contentSearch/contentEditing.spec.ts @@ -1,91 +1,142 @@ -import {expect, test} from '@playwright/test'; -import {dotCMSUtils, waitForVisibleAndCallback} from '../../utils/dotCMSUtils'; +import { expect, test } from "@playwright/test"; 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"; - + 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"; /** * 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(), async () => {}); - - 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(), async () => {}); - 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); - await contentUtils.deleteContent(page, genericContent1.newTitle); - }); +test("Delete a generic of content", async ({ page }) => { + const contentUtils = new ContentUtils(page); + 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 @@ -106,198 +157,280 @@ test('Validate required on text fields', async ({page}) => { /** * Test to validate you are able to add file assets importing from url */ -test('Validate adding file assets from URL', async ({page}) => { - const contentUtils = new ContentUtils(page); - - await contentUtils.addNewContentAction(page, fileAsset.locator, fileAsset.label); - await contentUtils.fillFileAssetForm({ - page, - host: fileAssetContent.host, - title: fileAssetContent.title, - editContent: true, - action: contentProperties.publishWfAction, - fromURL: fileAssetContent.fromURL - }); - await contentUtils.workflowExecutionValidationAndClose(page, 'Content saved'); - await expect(contentUtils.validateContentExist(page, 'DotCMS-logo.svg')).resolves.toBeTruthy(); +test("Validate adding file assets from URL", async ({ page }) => { + const contentUtils = new ContentUtils(page); + + await contentUtils.addNewContentAction( + page, + fileAsset.locator, + fileAsset.label, + ); + await contentUtils.fillFileAssetForm({ + page, + host: fileAssetContent.host, + title: fileAssetContent.title, + editContent: true, + action: contentProperties.publishWfAction, + fromURL: fileAssetContent.fromURL, + }); + 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 creating a new file */ -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); - await contentUtils.fillFileAssetForm({ - page, - host: fileAssetContent.host, - editContent: false, - title: fileAssetContent.title, - action: contentProperties.publishWfAction, - binaryFileName: fileAssetContent.newFileName, - binaryFileText: fileAssetContent.newFileText - }); - await contentUtils.workflowExecutionValidationAndClose(page, 'Content saved'); - await contentUtils.validateContentExist(page, fileAssetContent.newFileName).then(assert); +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, + ); + await contentUtils.fillFileAssetForm({ + page, + host: fileAssetContent.host, + editContent: false, + title: fileAssetContent.title, + action: contentProperties.publishWfAction, + binaryFileName: fileAssetContent.newFileName, + binaryFileText: fileAssetContent.newFileText, + }); + await contentUtils.workflowExecutionValidationAndClose(page, "Content saved"); + await contentUtils + .validateContentExist(page, fileAssetContent.newFileName) + .then(assert); }); /** * Test to validate you are able to edit file assets text */ -test('Validate you can 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) - ); - - await contentUtils.fillFileAssetForm({ - page, - host: fileAssetContent.host, - editContent: true, - title: fileAssetContent.title, - action: contentProperties.publishWfAction, - binaryFileName: fileAssetContent.newFileName, - binaryFileText: fileAssetContent.newFileTextEdited - }); - await contentUtils.workflowExecutionValidationAndClose(page, 'Content saved'); - - await contentUtils.selectTypeOnFilter(page, fileAsset.locator); - await (await contentUtils.getContentElement(page, fileAssetContent.newFileName)).click(); - const editIframe = page.frameLocator(iFramesLocators.dot_edit_iframe); - await expect(editIframe.getByRole('code')).toHaveText(fileAssetContent.newFileTextEdited); +test("Validate you can 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), + ); + + await contentUtils.fillFileAssetForm({ + page, + host: fileAssetContent.host, + editContent: true, + title: fileAssetContent.title, + action: contentProperties.publishWfAction, + binaryFileName: fileAssetContent.newFileName, + binaryFileText: fileAssetContent.newFileTextEdited, + }); + await contentUtils.workflowExecutionValidationAndClose(page, "Content saved"); + + await contentUtils.selectTypeOnFilter(page, fileAsset.locator); + await ( + await contentUtils.getContentElement(page, fileAssetContent.newFileName) + ).click(); + const editIframe = page.frameLocator(iFramesLocators.dot_edit_iframe); + await expect(editIframe.getByRole("code")).toHaveText( + fileAssetContent.newFileTextEdited, + ); }); /** * Test to validate you are able to remove file assets from the content */ -test('Validate you are able to delete file on binary fields', async ({page}) => { - const contentUtils = new ContentUtils(page); - const mainFrame = page.frameLocator(iFramesLocators.main_iframe); - - await contentUtils.selectTypeOnFilter(page, fileAsset.locator); - await waitForVisibleAndCallback(mainFrame.locator('#contentWrapper'), async () => {}); - 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 detailFrame = page.frameLocator(iFramesLocators.dot_edit_iframe); - await detailFrame.getByRole('button', { name: ' Remove' }).click(); - await waitForVisibleAndCallback(detailFrame.getByTestId('ui-message-icon-container'), async () => {}); - await detailFrame.getByText('Publish', { exact: true }).click(); - await expect(detailFrame.getByText('The field File Asset is')).toBeVisible(); +test("Validate you are able to delete file on binary fields", async ({ + page, +}) => { + const contentUtils = new ContentUtils(page); + const mainFrame = page.frameLocator(iFramesLocators.main_iframe); + + await contentUtils.selectTypeOnFilter(page, fileAsset.locator); + await waitForVisibleAndCallback( + mainFrame.locator("#contentWrapper"), + async () => {}, + ); + 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 detailFrame = page.frameLocator(iFramesLocators.dot_edit_iframe); + await detailFrame.getByRole("button", { name: " Remove" }).click(); + await waitForVisibleAndCallback( + detailFrame.getByTestId("ui-message-icon-container"), + async () => {}, + ); + await detailFrame.getByText("Publish", { exact: true }).click(); + await expect(detailFrame.getByText("The field File Asset is")).toBeVisible(); }); /** * Test to validate the get info on of the binary field on file assets */ -test('Validate file assets show corresponding information', async ({page}) => { - const contentUtils = new ContentUtils(page); - const mainFrame = page.frameLocator(iFramesLocators.main_iframe); - - await contentUtils.selectTypeOnFilter(page, fileAsset.locator); - await waitForVisibleAndCallback(mainFrame.locator('#contentWrapper'), async () => {}); - await (await contentUtils.getContentElement(page, fileAssetContent.newFileName)).click(); - await waitForVisibleAndCallback(page.getByRole('heading'), () => - expect.soft(page.getByRole('heading')).toContainText(fileAsset.label) - ); - - const detailFrame = page.frameLocator(iFramesLocators.dot_edit_iframe); - await detailFrame.getByTestId('info-btn').click(); - await waitForVisibleAndCallback(detailFrame.getByText('Bytes'), async () => {}); - await expect(detailFrame.getByText('Bytes')).toBeVisible(); - await expect(detailFrame.getByTestId('resource-link-FileLink')).toContainText("http"); - await expect(detailFrame.getByTestId('resource-link-Resource-Link')).not.toBeEmpty(); - await expect(detailFrame.getByTestId('resource-link-VersionPath')).not.toBeEmpty(); - await expect(detailFrame.getByTestId('resource-link-IdPath')).not.toBeEmpty(); +test("Validate file assets show corresponding information", async ({ + page, +}) => { + const contentUtils = new ContentUtils(page); + const mainFrame = page.frameLocator(iFramesLocators.main_iframe); + + await contentUtils.selectTypeOnFilter(page, fileAsset.locator); + await waitForVisibleAndCallback( + mainFrame.locator("#contentWrapper"), + async () => {}, + ); + await ( + await contentUtils.getContentElement(page, fileAssetContent.newFileName) + ).click(); + await waitForVisibleAndCallback(page.getByRole("heading"), () => + expect.soft(page.getByRole("heading")).toContainText(fileAsset.label), + ); + + const detailFrame = page.frameLocator(iFramesLocators.dot_edit_iframe); + await detailFrame.getByTestId("info-btn").click(); + await waitForVisibleAndCallback( + detailFrame.getByText("Bytes"), + async () => {}, + ); + await expect(detailFrame.getByText("Bytes")).toBeVisible(); + await expect(detailFrame.getByTestId("resource-link-FileLink")).toContainText( + "http", + ); + await expect( + detailFrame.getByTestId("resource-link-Resource-Link"), + ).not.toBeEmpty(); + await expect( + detailFrame.getByTestId("resource-link-VersionPath"), + ).not.toBeEmpty(); + await expect(detailFrame.getByTestId("resource-link-IdPath")).not.toBeEmpty(); }); //* Test to validate the download of binary fields on file assets -test('Validate the download of binary fields on file assets', async ({page}) => { - const contentUtils = new ContentUtils(page); - const mainFrame = page.frameLocator(iFramesLocators.main_iframe); - - await contentUtils.selectTypeOnFilter(page, fileAsset.locator); - await waitForVisibleAndCallback(mainFrame.locator('#contentWrapper'), async () => {}); - await (await contentUtils.getContentElement(page, fileAssetContent.newFileName)).click(); - await waitForVisibleAndCallback(page.getByRole('heading'), () => - expect.soft(page.getByRole('heading')).toContainText(fileAsset.label) - ); - const detailFrame = page.frameLocator(iFramesLocators.dot_edit_iframe); - const downloadLink = detailFrame.getByTestId('download-btn'); - await contentUtils.validateDownload(page, downloadLink); +test("Validate the download of binary fields on file assets", async ({ + page, +}) => { + const contentUtils = new ContentUtils(page); + const mainFrame = page.frameLocator(iFramesLocators.main_iframe); + + await contentUtils.selectTypeOnFilter(page, fileAsset.locator); + await waitForVisibleAndCallback( + mainFrame.locator("#contentWrapper"), + async () => {}, + ); + await ( + await contentUtils.getContentElement(page, fileAssetContent.newFileName) + ).click(); + await waitForVisibleAndCallback(page.getByRole("heading"), () => + expect.soft(page.getByRole("heading")).toContainText(fileAsset.label), + ); + const detailFrame = page.frameLocator(iFramesLocators.dot_edit_iframe); + const downloadLink = detailFrame.getByTestId("download-btn"); + await contentUtils.validateDownload(page, downloadLink); }); /** * Test to validate the required on file asset fields */ -test('Validate the required on file asset fields', async ({page}) => { - const contentUtils = new ContentUtils(page); - const detailsFrame = page.frameLocator(iFramesLocators.dot_iframe); - - await contentUtils.addNewContentAction(page, fileAsset.locator, fileAsset.label); - await contentUtils.fillFileAssetForm({ - page, - host: fileAssetContent.host, - editContent: false, - title: fileAssetContent.title, - action: contentProperties.publishWfAction - }); - await waitForVisibleAndCallback(detailsFrame.getByText('Error x'), async () => {}); - 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 contentUtils = new ContentUtils(page); + const detailsFrame = page.frameLocator(iFramesLocators.dot_iframe); + + await contentUtils.addNewContentAction( + page, + fileAsset.locator, + fileAsset.label, + ); + await contentUtils.fillFileAssetForm({ + page, + host: fileAssetContent.host, + editContent: false, + title: fileAssetContent.title, + action: contentProperties.publishWfAction, + }); + await waitForVisibleAndCallback( + detailsFrame.getByText("Error x"), + async () => {}, + ); + const 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 contentUtils = new ContentUtils(page); - const detailsFrame = page.frameLocator(iFramesLocators.dot_iframe); - - await contentUtils.addNewContentAction(page, fileAsset.locator, fileAsset.label); - await detailsFrame.locator('#fileName').fill('test'); - await contentUtils.fillFileAssetForm({ - page, - host: fileAssetContent.host, - editContent: false, - title: fileAssetContent.title, - binaryFileName: fileAssetContent.newFileName, - binaryFileText: fileAssetContent.newFileText - }); - 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 contentUtils = new ContentUtils(page); + const detailsFrame = page.frameLocator(iFramesLocators.dot_iframe); + + await contentUtils.addNewContentAction( + page, + fileAsset.locator, + fileAsset.label, + ); + await detailsFrame.locator("#fileName").fill("test"); + await contentUtils.fillFileAssetForm({ + page, + host: fileAssetContent.host, + editContent: false, + title: fileAssetContent.title, + binaryFileName: fileAssetContent.newFileName, + binaryFileText: fileAssetContent.newFileText, + }); + 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 contentUtils = new ContentUtils(page); - const detailsFrame = page.frameLocator(iFramesLocators.dot_iframe); - - await contentUtils.addNewContentAction(page, fileAsset.locator, fileAsset.label); - await detailsFrame.locator('#fileName').fill('test'); - await contentUtils.fillFileAssetForm({ - page, - host: fileAssetContent.host, - editContent: false, - title: fileAssetContent.title, - binaryFileName: fileAssetContent.newFileName, - binaryFileText: fileAssetContent.newFileText - }); - const replaceText = detailsFrame.getByText('Do you want to replace the'); - await waitForVisibleAndCallback(replaceText, () => 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 contentUtils = new ContentUtils(page); + const detailsFrame = page.frameLocator(iFramesLocators.dot_iframe); + + await contentUtils.addNewContentAction( + page, + fileAsset.locator, + fileAsset.label, + ); + await detailsFrame.locator("#fileName").fill("test"); + await contentUtils.fillFileAssetForm({ + page, + host: fileAssetContent.host, + editContent: false, + title: fileAssetContent.title, + binaryFileName: fileAssetContent.newFileName, + binaryFileText: fileAssetContent.newFileText, + }); + const replaceText = detailsFrame.getByText("Do you want to replace the"); + await waitForVisibleAndCallback(replaceText, () => + expect(replaceText).toBeVisible(), + ); + await detailsFrame.getByLabel("No").click(); + await expect(detailsFrame.locator("#fileName")).toHaveValue("test"); }); /** @@ -319,91 +452,125 @@ test('Validate the auto complete on FileName field rejecting change', async ({pa /** * Test to validate you are able to delete a file asset content */ -test('Delete a file asset content', async ({ page }) => { - await new ContentUtils(page).deleteContent(page, fileAssetContent.title); +test("Delete a file asset content", async ({ page }) => { + await new ContentUtils(page).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); - await contentUtils.fillPageAssetForm({ - 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, - }); - const dataFrame = page.frameLocator(iFramesLocators.dataTestId); - await waitForVisibleAndCallback(dataFrame.getByRole('banner'), async () => {}); - 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, + ); + await contentUtils.fillPageAssetForm({ + 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, + }); + 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); - await contentUtils.fillPageAssetForm({ - page, - title: "", - host: pageAssetContent.host, - template: pageAssetContent.template, - showOnMenu: pageAssetContent.showOnMenu, - action: contentProperties.publishWfAction - }); - 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("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, + ); + await contentUtils.fillPageAssetForm({ + page, + title: "", + host: pageAssetContent.host, + template: pageAssetContent.template, + showOnMenu: pageAssetContent.showOnMenu, + action: contentProperties.publishWfAction, + }); + 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); - await contentUtils.fillPageAssetForm({ - page, - title: pageAssetContent.title, - host: pageAssetContent.host, - template: pageAssetContent.template, - showOnMenu: pageAssetContent.showOnMenu - }); - - 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, + ); + await contentUtils.fillPageAssetForm({ + page, + title: pageAssetContent.title, + host: pageAssetContent.host, + template: pageAssetContent.template, + showOnMenu: pageAssetContent.showOnMenu, + }); + + await expect(detailFrame.locator("#url")).toHaveValue( + pageAssetContent.title.toLowerCase(), + ); + await expect(detailFrame.locator("#friendlyName")).toHaveValue( + pageAssetContent.title, + ); }); /** * Test to validate you are able to unpublish a page asset */ -test('Validate you are able to unpublish pages', async ({page}) => { - const contentUtils = new ContentUtils(page); - await contentUtils.selectTypeOnFilter(page, pageAsset.locator); - await contentUtils.performWorkflowAction(page, pageAssetContent.title, contentProperties.unpublishWfAction); - await contentUtils.getContentState(page, pageAssetContent.title).then(assert); +test("Validate you are able to unpublish pages", async ({ page }) => { + const contentUtils = new ContentUtils(page); + await contentUtils.selectTypeOnFilter(page, pageAsset.locator); + await contentUtils.performWorkflowAction( + page, + pageAssetContent.title, + contentProperties.unpublishWfAction, + ); + await contentUtils.getContentState(page, pageAssetContent.title).then(assert); }); /** * Test to validate you are able to delete pages */ -test('Validate you are able to delete pages', async ({page}) => { - const contentUtils = new ContentUtils(page); - await contentUtils.selectTypeOnFilter(page, pageAsset.locator); - await contentUtils.deleteContent(page, pageAssetContent.title); -}); \ No newline at end of file +test("Validate you are able to delete pages", async ({ page }) => { + const contentUtils = new ContentUtils(page); + await contentUtils.selectTypeOnFilter(page, pageAsset.locator); + await contentUtils.deleteContent(page, pageAssetContent.title); +}); 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 b4dea3712293..e5f701b6b0e5 100644 --- a/e2e/dotcms-e2e-node/frontend/tests/contentSearch/portletIntegrity.spec.ts +++ b/e2e/dotcms-e2e-node/frontend/tests/contentSearch/portletIntegrity.spec.ts @@ -224,14 +224,14 @@ test("Validate the clear button in the search filter", async ({ page }) => { // Validate the search filter has been cleared await expect( - iframe - .locator('input[name="scheme_id_select"]') - , - ).toHaveAttribute("value", "catchall"); - await expect( - iframe.locator('input[name="step_id_select"]'), + iframe.locator('input[name="scheme_id_select"]'), ).toHaveAttribute("value", "catchall"); - await expect(iframe.locator("#showingSelect")).toHaveAttribute("value", + await expect(iframe.locator('input[name="step_id_select"]')).toHaveAttribute( + "value", + "catchall", + ); + await expect(iframe.locator("#showingSelect")).toHaveAttribute( + "value", "All", ); }); @@ -250,9 +250,7 @@ test("Validate the hide button collapse the filter", async ({ page }) => { ); await page.waitForTimeout(1000); - await expect( - iframe.getByRole("link", { name: "Advanced" }), - ).toBeHidden(); + await expect(iframe.getByRole("link", { name: "Advanced" })).toBeHidden(); // Click on the hide button await iframe.getByRole("link", { name: "Hide" }).click(); 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 3b7a07021d81..c42804890012 100644 --- a/e2e/dotcms-e2e-node/frontend/tests/login/login.spec.ts +++ b/e2e/dotcms-e2e-node/frontend/tests/login/login.spec.ts @@ -1,47 +1,49 @@ -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}); - }); -}); \ No newline at end of file + // Assertion and further test steps + await expect(page.getByTestId("message")).toBeVisible({ timeout: 30000 }); + }); +}); diff --git a/e2e/dotcms-e2e-node/frontend/utils/contentUtils.ts b/e2e/dotcms-e2e-node/frontend/utils/contentUtils.ts index bb9e9ca9c7bd..009dd0b77ece 100644 --- a/e2e/dotcms-e2e-node/frontend/utils/contentUtils.ts +++ b/e2e/dotcms-e2e-node/frontend/utils/contentUtils.ts @@ -1,391 +1,505 @@ -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"; - +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, 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 () => { - }); - const editor = editFrame.getByLabel('Editor content;Press Alt+F1'); - await editor.click(); // Focus on the editor - await page.keyboard.press('Control+A'); // Select all text (Cmd+A for Mac) - await page.keyboard.press('Backspace'); - 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); + 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, + 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 () => {}, + ); + const editor = editFrame.getByLabel("Editor content;Press Alt+F1"); + await editor.click(); // Focus on the editor + await page.keyboard.press("Control+A"); // Select all text (Cmd+A for Mac) + await page.keyboard.press("Backspace"); + 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); }); - - 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()); + 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(); + } } - /** - * 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(); + 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 () => {}, + ); } - /** - * 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(); + await waitForVisibleAndCallback(dotIframe.locator("#title"), async () => { + await dotIframe.locator("#title").fill(title); + }); - // Click on show query - await iframe.getByText('Show Query').click(); + if (action) { + await dotIframe.getByText(action).first().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); - - 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; + } + + /** + * 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"), + 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(); + } + + /** + * 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)"), + async () => {}, + ); + 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; + } } - /** - * 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; + 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(); + } } - - - /** - * 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'); + 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; } + 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 waitForVisibleAndCallback( + iframe.locator("#contentWrapper"), + async () => {}, + ); + } else if (contentState === "archived") { + await this.performWorkflowAction( + page, + title, + contentProperties.deleteWfAction, + ); + return; + } - /** - * 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 waitForVisibleAndCallback(iframe.locator('#contentWrapper'), async () => {}); - } else if (contentState === 'archived') { - await this.performWorkflowAction(page, title, contentProperties.deleteWfAction); - return; - } - - await page.waitForLoadState(); - } + await page.waitForLoadState(); } - - /** - * 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()); + } + + /** + * 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 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; + 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(), + ); + } + + /** + * 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"); + } } - - /** - * 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(); + 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(); } - - - /** - * Validate the download of a file - * @param page - * @param downloadTriggerSelector - */ - async validateDownload(page: Page, downloadTriggerSelector: Locator) { - // Start waiting for the download event - const downloadPromise = page.waitForEvent('download'); - - // Trigger the download - await downloadTriggerSelector.click(); - - // Wait for the download to complete - const download = await downloadPromise; - - // Assert the download was successful - const fileName = download.suggestedFilename(); - console.log(`Downloaded file: ${fileName}`); - expect(fileName).toBeTruthy(); + 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(); + } + + /** + * Validate the download of a file + * @param page + * @param downloadTriggerSelector + */ + async validateDownload(page: Page, downloadTriggerSelector: Locator) { + // Start waiting for the download event + const downloadPromise = page.waitForEvent("download"); + + // Trigger the download + await downloadTriggerSelector.click(); + + // Wait for the download to complete + const download = await downloadPromise; + + // Assert the download was successful + const fileName = download.suggestedFilename(); + console.log(`Downloaded file: ${fileName}`); + expect(fileName).toBeTruthy(); + } } /** * 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; - editContent : boolean; - fileName?: string; - fromURL?: string; - binaryFileName?: string; - binaryFileText?: 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 2ab35d19b792..1a81af52d9cb 100644 --- a/e2e/dotcms-e2e-node/frontend/utils/dotCMSUtils.ts +++ b/e2e/dotcms-e2e-node/frontend/utils/dotCMSUtils.ts @@ -1,38 +1,47 @@ -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 { + /** + * 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(); + } } /** @@ -40,9 +49,12 @@ 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 @@ -50,9 +62,13 @@ export const waitFor = async (locator: Locator, state: "attached" | "detached" | * @param state * @param callback */ -export const waitForAndCallback = async (locator: Locator, state: "attached" | "detached" | "visible" | "hidden", callback: () => Promise): Promise => { - await waitFor(locator, state); - await callback(); +export const waitForAndCallback = async ( + locator: Locator, + state: "attached" | "detached" | "visible" | "hidden", + callback: () => Promise, +): Promise => { + await waitFor(locator, state); + await callback(); }; /** @@ -60,6 +76,9 @@ export const waitForAndCallback = async (locator: Locator, state: "attached" | " * @param locator * @param callback */ -export const waitForVisibleAndCallback = async (locator: Locator, callback: () => Promise): Promise => { - await waitForAndCallback(locator, 'visible', callback); -}; \ No newline at end of file +export const waitForVisibleAndCallback = async ( + locator: Locator, + callback: () => Promise, +): Promise => { + await waitForAndCallback(locator, "visible", callback); +}; From 31219baa8613c19ae67ea86e8d1c2912ff1ef358 Mon Sep 17 00:00:00 2001 From: Nicolas Molina Date: Mon, 13 Jan 2025 19:16:44 -0400 Subject: [PATCH 12/25] chore(e2e): add test for new content editor --- .../locators/navigation/menuLocators.ts | 3 + e2e/dotcms-e2e-node/frontend/package.json | 4 +- .../frontend/pages/contentTypeForm.page.ts | 19 +++++ .../pages/listingContentTypes.page.ts | 9 --- .../pages/listingContentTypes.pages.ts | 76 +++++++++++++++++++ .../frontend/pages/newEditContentForm.page.ts | 14 ++++ .../tests/editContent/newEditContent.spec.ts | 35 +++++++++ e2e/dotcms-e2e-node/frontend/yarn.lock | 5 ++ 8 files changed, 155 insertions(+), 10 deletions(-) create mode 100644 e2e/dotcms-e2e-node/frontend/pages/contentTypeForm.page.ts delete mode 100644 e2e/dotcms-e2e-node/frontend/pages/listingContentTypes.page.ts create mode 100644 e2e/dotcms-e2e-node/frontend/pages/listingContentTypes.pages.ts create mode 100644 e2e/dotcms-e2e-node/frontend/pages/newEditContentForm.page.ts create mode 100644 e2e/dotcms-e2e-node/frontend/tests/editContent/newEditContent.spec.ts diff --git a/e2e/dotcms-e2e-node/frontend/locators/navigation/menuLocators.ts b/e2e/dotcms-e2e-node/frontend/locators/navigation/menuLocators.ts index 58351841889f..79e4992638bc 100644 --- a/e2e/dotcms-e2e-node/frontend/locators/navigation/menuLocators.ts +++ b/e2e/dotcms-e2e-node/frontend/locators/navigation/menuLocators.ts @@ -3,6 +3,7 @@ import { Page, Locator } from "@playwright/test"; export class GroupEntriesLocators { readonly SITE: Locator; readonly CONTENT: Locator; + readonly CONTENT_MODEL: Locator; readonly SCHEMA: Locator; constructor(page: Page) { @@ -10,6 +11,8 @@ export class GroupEntriesLocators { this.CONTENT = page .getByRole("complementary") .getByText("Content", { exact: true }); + + this.CONTENT_MODEL = page.getByText("Content Model"); this.SCHEMA = page.getByText("Schema"); } } diff --git a/e2e/dotcms-e2e-node/frontend/package.json b/e2e/dotcms-e2e-node/frontend/package.json index a4a702750148..4bf5df997983 100644 --- a/e2e/dotcms-e2e-node/frontend/package.json +++ b/e2e/dotcms-e2e-node/frontend/package.json @@ -5,6 +5,7 @@ "license": "MIT", "devDependencies": { "@eslint/js": "^9.17.0", + "@faker-js/faker": "9.3.0", "@playwright/test": "^1.48.2", "@types/node": "^22.5.4", "@typescript-eslint/eslint-plugin": "^8.19.0", @@ -22,10 +23,11 @@ }, "scripts": { "show-report": "if [[ \"$CURRENT_ENV\" != \"ci\" ]]; then yarn playwright show-report; fi", - "start": "PLAYWRIGHT_JUNIT_SUITE_ID=nodee2etestsuite PLAYWRIGHT_JUNIT_SUITE_NAME='E2E Node Test Suite' PLAYWRIGHT_JUNIT_OUTPUT_FILE='../target/failsafe-reports/TEST-e2e-node-results.xml' yarn playwright test ${PLAYWRIGHT_SPECIFIC} ${PLAYWRIGHT_DEBUG}; yarn run show-report", + "start": "PLAYWRIGHT_JUNIT_SUITE_ID=nodee2etestsuite PLAYWRIGHT_JUNIT_SUITE_NAME='E2E Node Test Suite' PLAYWRIGHT_JUNIT_OUTPUT_FILE='../target/failsafe-reports/TEST-e2e-node-results.xml' yarn playwright test ${PLAYWRIGHT_SPECIFIC} ${PLAYWRIGHT_DEBUG} --ui; yarn run show-report", "start-local": "CURRENT_ENV=local yarn run start", "start-dev": "CURRENT_ENV=dev yarn run start", "start-ci": "CURRENT_ENV=ci yarn run start", + "codegen": "yarn playwright codegen", "post-testing": "PLAYWRIGHT_JUNIT_OUTPUT_FILE='../target/failsafe-reports/TEST-e2e-node-results.xml' node index.js", "format": "prettier --write .", "lint": "eslint .", diff --git a/e2e/dotcms-e2e-node/frontend/pages/contentTypeForm.page.ts b/e2e/dotcms-e2e-node/frontend/pages/contentTypeForm.page.ts new file mode 100644 index 000000000000..60dbf34019a8 --- /dev/null +++ b/e2e/dotcms-e2e-node/frontend/pages/contentTypeForm.page.ts @@ -0,0 +1,19 @@ +import { Page } from "@playwright/test"; + +export class ContentTypeFormPage { + constructor(private page: Page) {} + + async fillNewContentType() { + await this.addTitleField(); + } + + async addTitleField() { + const dropZone = this.page.locator('div[dragula="fields-bag"]'); + await this.page + .locator("li") + .getByText("Text", { exact: true }) + .dragTo(dropZone); + await this.page.locator("input#name").fill("Text Field"); + await this.page.getByTestId("dotDialogAcceptAction").click(); + } +} diff --git a/e2e/dotcms-e2e-node/frontend/pages/listingContentTypes.page.ts b/e2e/dotcms-e2e-node/frontend/pages/listingContentTypes.page.ts deleted file mode 100644 index 5b669c08c1fe..000000000000 --- a/e2e/dotcms-e2e-node/frontend/pages/listingContentTypes.page.ts +++ /dev/null @@ -1,9 +0,0 @@ -import { Page } from "@playwright/test"; - -export class ListingContentTypesPage { - constructor(private page: Page) {} - - async goTo() { - await this.page.goto("/content-types-angular"); - } -} diff --git a/e2e/dotcms-e2e-node/frontend/pages/listingContentTypes.pages.ts b/e2e/dotcms-e2e-node/frontend/pages/listingContentTypes.pages.ts new file mode 100644 index 000000000000..329e1077526c --- /dev/null +++ b/e2e/dotcms-e2e-node/frontend/pages/listingContentTypes.pages.ts @@ -0,0 +1,76 @@ +import { Page, expect } from "@playwright/test"; +import { MenuEntriesLocators } from "../locators/navigation/menuLocators"; +import { + GroupEntriesLocators, + ToolEntriesLocators, +} from "../locators/navigation/menuLocators"; +import { dotCMSUtils } from "../utils/dotCMSUtils"; + +export class ListingContentTypesPage { + private cmsUtils: dotCMSUtils; + private menuLocators: MenuEntriesLocators; + private groupsLocators: GroupEntriesLocators; + private toolsLocators: ToolEntriesLocators; + + constructor(private page: Page) { + this.cmsUtils = new dotCMSUtils(); + this.menuLocators = new MenuEntriesLocators(this.page); + this.groupsLocators = new GroupEntriesLocators(this.page); + this.toolsLocators = new ToolEntriesLocators(this.page); + } + + async goTo() { + // 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 this.cmsUtils.login(this.page, username, password); + await this.cmsUtils.navigate( + this.menuLocators.EXPAND, + this.groupsLocators.CONTENT_MODEL, + this.toolsLocators.CONTENT_TYPES, + ); + + // Validate the portlet title + const breadcrumbLocator = this.page.locator("p-breadcrumb"); + await expect(breadcrumbLocator).toContainText("Content Types"); + } + + async addNewContentType(name: string) { + await this.page.getByRole("button", { name: "" }).click(); + await this.page.getByLabel("Content").locator("a").click(); + await this.page + .locator('[data-test-id="content-type__new-content-banner"] div') + .nth(2) + .click(); + + await this.page.getByLabel("Content Name").fill(name); + await this.page.getByTestId("dotDialogAcceptAction").click(); + } + + async gotToContentTypes() { + await this.toolsLocators.CONTENT_TYPES.first().click(); + } + + async gotToContentType(contentType: string) { + const capitalized = + contentType.charAt(0).toUpperCase() + contentType.slice(1); + + await this.page + .getByTestId(`row-${capitalized}`) + .getByRole("link", { name: "View (0)" }) + .click(); + await this.page + .locator('iframe[name="detailFrame"]') + .contentFrame() + .locator("#dijit_form_DropDownButton_0") + .click(); + await this.page + .locator('iframe[name="detailFrame"]') + .contentFrame() + .getByLabel("▼") + .getByText("Add New Content") + .click(); + } +} diff --git a/e2e/dotcms-e2e-node/frontend/pages/newEditContentForm.page.ts b/e2e/dotcms-e2e-node/frontend/pages/newEditContentForm.page.ts new file mode 100644 index 000000000000..cb82d82f3c73 --- /dev/null +++ b/e2e/dotcms-e2e-node/frontend/pages/newEditContentForm.page.ts @@ -0,0 +1,14 @@ +import { Page } from "@playwright/test"; + +export class NewEditContentFormPage { + constructor(private page: Page) {} + + async fillTextField(text: string) { + await this.page.getByTestId("textField").fill(text); + } + + async save() { + await this.page.getByRole("button", { name: "Save" }).click(); + await this.page.waitForResponse("**/api/v1/workflow/actions/**"); + } +} diff --git a/e2e/dotcms-e2e-node/frontend/tests/editContent/newEditContent.spec.ts b/e2e/dotcms-e2e-node/frontend/tests/editContent/newEditContent.spec.ts new file mode 100644 index 000000000000..62f51d17e4bb --- /dev/null +++ b/e2e/dotcms-e2e-node/frontend/tests/editContent/newEditContent.spec.ts @@ -0,0 +1,35 @@ +import { test, expect } from "@playwright/test"; +import { faker } from "@faker-js/faker"; +import { ListingContentTypesPage } from "../../pages/listingContentTypes.pages"; +import { ContentTypeFormPage } from "../../pages/contentTypeForm.page"; +import { NewEditContentFormPage } from "../../pages/newEditContentForm.page"; + +test.beforeEach("Navigate to content types", async ({ page }) => { + const listingContentTypesPage = new ListingContentTypesPage(page); + const contentTypeFormPage = new ContentTypeFormPage(page); + await listingContentTypesPage.goTo(); + + const contentTypeName = faker.lorem.word().toLocaleLowerCase(); + + await listingContentTypesPage.addNewContentType(contentTypeName); + await contentTypeFormPage.fillNewContentType(); + await listingContentTypesPage.gotToContentTypes(); + await listingContentTypesPage.gotToContentType(contentTypeName); +}); + +test.describe("text field", () => { + test("should save a text field", async ({ page }) => { + const newEditContentFormPage = new NewEditContentFormPage(page); + + const locatorField = page.getByTestId("textField"); + + await expect(locatorField).toBeVisible(); + + const textFieldValue = faker.lorem.word(); + + await newEditContentFormPage.fillTextField(textFieldValue); + await newEditContentFormPage.save(); + + await expect(locatorField).toHaveValue(textFieldValue); + }); +}); diff --git a/e2e/dotcms-e2e-node/frontend/yarn.lock b/e2e/dotcms-e2e-node/frontend/yarn.lock index 48c37d48d78b..05c2cf00a5b0 100644 --- a/e2e/dotcms-e2e-node/frontend/yarn.lock +++ b/e2e/dotcms-e2e-node/frontend/yarn.lock @@ -62,6 +62,11 @@ dependencies: levn "^0.4.1" +"@faker-js/faker@9.3.0": + version "9.3.0" + resolved "https://registry.npmjs.org/@faker-js/faker/-/faker-9.3.0.tgz#ef398dab34c67faaa0e348318c905eae3564fa58" + integrity sha512-r0tJ3ZOkMd9xsu3VRfqlFR6cz0V/jFYRswAIpC+m/DIfAUXq7g8N7wTAlhSANySXYGKzGryfDXwtwsY8TxEIDw== + "@humanfs/core@^0.19.1": version "0.19.1" resolved "https://registry.npmjs.org/@humanfs/core/-/core-0.19.1.tgz#17c55ca7d426733fe3c561906b8173c336b40a77" From 4a960d92c7a28385b1098e9e02585a80133d2628 Mon Sep 17 00:00:00 2001 From: Nicolas Molina Date: Mon, 13 Jan 2025 19:34:57 -0400 Subject: [PATCH 13/25] chore(e2e): add after hook to delete content type --- .../pages/listingContentTypes.pages.ts | 22 ++++++++++++++----- .../tests/editContent/newEditContent.spec.ts | 15 ++++++++----- 2 files changed, 27 insertions(+), 10 deletions(-) diff --git a/e2e/dotcms-e2e-node/frontend/pages/listingContentTypes.pages.ts b/e2e/dotcms-e2e-node/frontend/pages/listingContentTypes.pages.ts index 329e1077526c..4d41a0a1f5e9 100644 --- a/e2e/dotcms-e2e-node/frontend/pages/listingContentTypes.pages.ts +++ b/e2e/dotcms-e2e-node/frontend/pages/listingContentTypes.pages.ts @@ -19,6 +19,10 @@ export class ListingContentTypesPage { this.toolsLocators = new ToolEntriesLocators(this.page); } + async goToUrl() { + await this.page.goto("/dotAdmin/#/content-types-angular"); + } + async goTo() { // Get the username and password from the environment variables const username = process.env.USERNAME as string; @@ -49,11 +53,7 @@ export class ListingContentTypesPage { await this.page.getByTestId("dotDialogAcceptAction").click(); } - async gotToContentTypes() { - await this.toolsLocators.CONTENT_TYPES.first().click(); - } - - async gotToContentType(contentType: string) { + async goToAddNewContentType(contentType: string) { const capitalized = contentType.charAt(0).toUpperCase() + contentType.slice(1); @@ -73,4 +73,16 @@ export class ListingContentTypesPage { .getByText("Add New Content") .click(); } + + async deleteContentType(contentType: string) { + const capitalized = + contentType.charAt(0).toUpperCase() + contentType.slice(1); + + await this.page + .getByTestId(`row-${capitalized}`) + .getByTestId("dot-menu-button") + .click(); + await this.page.getByLabel("Delete").locator("a").click(); + await this.page.getByRole("button", { name: "Delete" }).click(); + } } diff --git a/e2e/dotcms-e2e-node/frontend/tests/editContent/newEditContent.spec.ts b/e2e/dotcms-e2e-node/frontend/tests/editContent/newEditContent.spec.ts index 62f51d17e4bb..73c32b598a48 100644 --- a/e2e/dotcms-e2e-node/frontend/tests/editContent/newEditContent.spec.ts +++ b/e2e/dotcms-e2e-node/frontend/tests/editContent/newEditContent.spec.ts @@ -4,17 +4,22 @@ import { ListingContentTypesPage } from "../../pages/listingContentTypes.pages"; import { ContentTypeFormPage } from "../../pages/contentTypeForm.page"; import { NewEditContentFormPage } from "../../pages/newEditContentForm.page"; +const contentTypeName = faker.lorem.word().toLocaleLowerCase(); + test.beforeEach("Navigate to content types", async ({ page }) => { const listingContentTypesPage = new ListingContentTypesPage(page); const contentTypeFormPage = new ContentTypeFormPage(page); await listingContentTypesPage.goTo(); - - const contentTypeName = faker.lorem.word().toLocaleLowerCase(); - await listingContentTypesPage.addNewContentType(contentTypeName); await contentTypeFormPage.fillNewContentType(); - await listingContentTypesPage.gotToContentTypes(); - await listingContentTypesPage.gotToContentType(contentTypeName); + await listingContentTypesPage.goToUrl(); + await listingContentTypesPage.goToAddNewContentType(contentTypeName); +}); + +test.afterEach(async ({ page }) => { + const listingContentTypesPage = new ListingContentTypesPage(page); + await listingContentTypesPage.goToUrl(); + await listingContentTypesPage.deleteContentType(contentTypeName); }); test.describe("text field", () => { From 32e2208dcac6516106a218a322154bd61868c013 Mon Sep 17 00:00:00 2001 From: Nicolas Molina Date: Tue, 14 Jan 2025 15:19:43 -0400 Subject: [PATCH 14/25] chore(e2e): update features flags --- .../frontend/pages/textField.page.ts | 8 +- .../fields/textField.spec.ts} | 29 ++++- e2e/dotcms-e2e-node/frontend/utils/api.ts | 116 +----------------- 3 files changed, 32 insertions(+), 121 deletions(-) rename e2e/dotcms-e2e-node/frontend/tests/{editContent/newEditContent.spec.ts => newEditContent/fields/textField.spec.ts} (59%) diff --git a/e2e/dotcms-e2e-node/frontend/pages/textField.page.ts b/e2e/dotcms-e2e-node/frontend/pages/textField.page.ts index b18ea197208c..ad7d8e2487b1 100644 --- a/e2e/dotcms-e2e-node/frontend/pages/textField.page.ts +++ b/e2e/dotcms-e2e-node/frontend/pages/textField.page.ts @@ -1,12 +1,10 @@ -import { expect, Page } from "@playwright/test"; +import { Page } from "@playwright/test"; export class TextFieldPage { constructor(private page: Page) {} - async fill(vairableName: string, value: string) { - const input = this.page.locator(`input#${vairableName}`); + async fill(variableName: string, value: string) { + const input = this.page.locator(`input#${variableName}`); await input.fill(value); - - await expect(input).toHaveValue(value); } } diff --git a/e2e/dotcms-e2e-node/frontend/tests/editContent/newEditContent.spec.ts b/e2e/dotcms-e2e-node/frontend/tests/newEditContent/fields/textField.spec.ts similarity index 59% rename from e2e/dotcms-e2e-node/frontend/tests/editContent/newEditContent.spec.ts rename to e2e/dotcms-e2e-node/frontend/tests/newEditContent/fields/textField.spec.ts index 73c32b598a48..189c55ec6420 100644 --- a/e2e/dotcms-e2e-node/frontend/tests/editContent/newEditContent.spec.ts +++ b/e2e/dotcms-e2e-node/frontend/tests/newEditContent/fields/textField.spec.ts @@ -1,12 +1,13 @@ import { test, expect } from "@playwright/test"; import { faker } from "@faker-js/faker"; -import { ListingContentTypesPage } from "../../pages/listingContentTypes.pages"; -import { ContentTypeFormPage } from "../../pages/contentTypeForm.page"; -import { NewEditContentFormPage } from "../../pages/newEditContentForm.page"; +import { ListingContentTypesPage } from "../../../pages/listingContentTypes.pages"; +import { ContentTypeFormPage } from "../../../pages/contentTypeForm.page"; +import { NewEditContentFormPage } from "../../../pages/newEditContentForm.page"; +import { updateFeatureFlag } from "../../../utils/api"; const contentTypeName = faker.lorem.word().toLocaleLowerCase(); -test.beforeEach("Navigate to content types", async ({ page }) => { +test.beforeEach("Navigate to content types", async ({ page, request }) => { const listingContentTypesPage = new ListingContentTypesPage(page); const contentTypeFormPage = new ContentTypeFormPage(page); await listingContentTypesPage.goTo(); @@ -14,12 +15,30 @@ test.beforeEach("Navigate to content types", async ({ page }) => { await contentTypeFormPage.fillNewContentType(); await listingContentTypesPage.goToUrl(); await listingContentTypesPage.goToAddNewContentType(contentTypeName); + + await updateFeatureFlag(request, { + key: "DOT_FEATURE_FLAG_NEW_EDIT_PAGE", + value: true, + }); + await updateFeatureFlag(request, { + key: "DOT_CONTENT_EDITOR2_ENABLED", + value: true, + }); }); -test.afterEach(async ({ page }) => { +test.afterEach(async ({ page, request }) => { const listingContentTypesPage = new ListingContentTypesPage(page); await listingContentTypesPage.goToUrl(); await listingContentTypesPage.deleteContentType(contentTypeName); + + await updateFeatureFlag(request, { + key: "DOT_FEATURE_FLAG_NEW_EDIT_PAGE", + value: false, + }); + await updateFeatureFlag(request, { + key: "DOT_CONTENT_EDITOR2_ENABLED", + value: false, + }); }); test.describe("text field", () => { diff --git a/e2e/dotcms-e2e-node/frontend/utils/api.ts b/e2e/dotcms-e2e-node/frontend/utils/api.ts index a0e79d472089..d6a6abbf99c2 100644 --- a/e2e/dotcms-e2e-node/frontend/utils/api.ts +++ b/e2e/dotcms-e2e-node/frontend/utils/api.ts @@ -1,117 +1,11 @@ import { APIRequestContext, expect } from "@playwright/test"; -export async function createBasicContentType(request: APIRequestContext) { - const data = { - defaultType: false, - icon: "new_releases", - fixed: false, - system: false, - clazz: "com.dotcms.contenttype.model.type.ImmutableSimpleContentType", - description: "Include all fields", - host: "48190c8c-42c4-46af-8d1a-0cd5db894797", - folder: "SYSTEM_FOLDER", - name: "BasicContentType", - systemActionMappings: { - NEW: "", - }, - metadata: { - CONTENT_EDITOR2_ENABLED: true, - }, - workflow: ["d61a59e1-a49c-46f2-a929-db2b4bfa88b2"], - }; - - const endpoint = `/api/v1/contenttype`; - - const contentTypeResponse = await request.post(endpoint, { - data, - }); - - expect(contentTypeResponse.status()).toBe(201); - - const contentTypeResponseBody = await contentTypeResponse.json(); - expect(contentTypeResponseBody.id).toBeDefined(); - - return contentTypeResponseBody; -} - -export async function addTextFieldToContentType( +export async function updateFeatureFlag( request: APIRequestContext, - contentTypeId: string, + data: Record, ) { - const data = { - layout: [ - { - divider: { - clazz: "com.dotcms.contenttype.model.field.ImmutableRowField", - contentTypeId, - dataType: "SYSTEM", - fieldContentTypeProperties: [], - fieldType: "Row", - fieldTypeLabel: "Row", - fieldVariables: [], - fixed: false, - forceIncludeInApi: false, - iDate: 1735844752000, - indexed: false, - listed: false, - modDate: 1735844752000, - name: "Row Field", - readOnly: false, - required: false, - searchable: false, - sortOrder: -1, - unique: false, - }, - columns: [ - { - columnDivider: { - clazz: "com.dotcms.contenttype.model.field.ImmutableColumnField", - contentTypeId, - dataType: "SYSTEM", - fieldContentTypeProperties: [], - fieldType: "Column", - fieldTypeLabel: "Column", - fieldVariables: [], - fixed: false, - forceIncludeInApi: false, - iDate: 1735844752000, - indexed: false, - listed: false, - modDate: 1735844752000, - name: "Column Field", - readOnly: false, - required: false, - searchable: false, - sortOrder: -1, - unique: false, - }, - fields: [ - { - clazz: "com.dotcms.contenttype.model.field.ImmutableTextField", - name: "Text Field", - dataType: "TEXT", - regexCheck: "", - defaultValue: "", - hint: "Text hint", - required: true, - searchable: false, - indexed: false, - listed: false, - unique: false, - id: null, - }, - ], - }, - ], - }, - ], - }; - - const endpoint = `/api/v3/contenttype/${contentTypeId}/fields/move`; - - const response = await request.put(endpoint, { - data, - }); + const endpoint = `/api/v1/system-table/`; + const contentTypeResponse = await request.post(endpoint, { data }); - expect(response.status()).toBe(200); + expect(contentTypeResponse.status()).toBe(200); } From 2fb70a180444387ec75ae924751ee22d1f01b74e Mon Sep 17 00:00:00 2001 From: Nicolas Molina Date: Tue, 14 Jan 2025 16:01:57 -0400 Subject: [PATCH 15/25] chore(e2e): fix error --- e2e/dotcms-e2e-node/frontend/package.json | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/e2e/dotcms-e2e-node/frontend/package.json b/e2e/dotcms-e2e-node/frontend/package.json index 4bf5df997983..d1d2bb5179b2 100644 --- a/e2e/dotcms-e2e-node/frontend/package.json +++ b/e2e/dotcms-e2e-node/frontend/package.json @@ -23,11 +23,12 @@ }, "scripts": { "show-report": "if [[ \"$CURRENT_ENV\" != \"ci\" ]]; then yarn playwright show-report; fi", - "start": "PLAYWRIGHT_JUNIT_SUITE_ID=nodee2etestsuite PLAYWRIGHT_JUNIT_SUITE_NAME='E2E Node Test Suite' PLAYWRIGHT_JUNIT_OUTPUT_FILE='../target/failsafe-reports/TEST-e2e-node-results.xml' yarn playwright test ${PLAYWRIGHT_SPECIFIC} ${PLAYWRIGHT_DEBUG} --ui; yarn run show-report", + "start": "PLAYWRIGHT_JUNIT_SUITE_ID=nodee2etestsuite PLAYWRIGHT_JUNIT_SUITE_NAME='E2E Node Test Suite' PLAYWRIGHT_JUNIT_OUTPUT_FILE='../target/failsafe-reports/TEST-e2e-node-results.xml' yarn playwright test ${PLAYWRIGHT_SPECIFIC} ${PLAYWRIGHT_DEBUG}; yarn run show-report", "start-local": "CURRENT_ENV=local yarn run start", "start-dev": "CURRENT_ENV=dev yarn run start", "start-ci": "CURRENT_ENV=ci yarn run start", "codegen": "yarn playwright codegen", + "ui": "yarn playwright test --ui", "post-testing": "PLAYWRIGHT_JUNIT_OUTPUT_FILE='../target/failsafe-reports/TEST-e2e-node-results.xml' node index.js", "format": "prettier --write .", "lint": "eslint .", From f91343f5d166dd7d9dba0f9f8badcba8675df45c Mon Sep 17 00:00:00 2001 From: Nicolas Molina Date: Wed, 15 Jan 2025 11:51:04 -0400 Subject: [PATCH 16/25] chore(e2e): enable basic auth in endpoints --- e2e/dotcms-e2e-node/frontend/utils/api.ts | 16 +++++++++++++++- 1 file changed, 15 insertions(+), 1 deletion(-) diff --git a/e2e/dotcms-e2e-node/frontend/utils/api.ts b/e2e/dotcms-e2e-node/frontend/utils/api.ts index d6a6abbf99c2..f619f0827bd0 100644 --- a/e2e/dotcms-e2e-node/frontend/utils/api.ts +++ b/e2e/dotcms-e2e-node/frontend/utils/api.ts @@ -1,11 +1,25 @@ import { APIRequestContext, expect } from "@playwright/test"; +import { admin1 } from "../tests/login/credentialsData"; export async function updateFeatureFlag( request: APIRequestContext, data: Record, ) { const endpoint = `/api/v1/system-table/`; - const contentTypeResponse = await request.post(endpoint, { data }); + const contentTypeResponse = await request.post(endpoint, { + data, + headers: { + Authorization: generateBase64Credentials( + admin1.username, + admin1.password, + ), + }, + }); expect(contentTypeResponse.status()).toBe(200); } + +export function generateBase64Credentials(username: string, password: string) { + const credentialsBase64 = btoa(`${username}:${password}`); + return `Basic ${credentialsBase64}`; +} From 0717689532a9bfb6a50379d3ef3d304745adf226 Mon Sep 17 00:00:00 2001 From: Nicolas Molina Date: Wed, 15 Jan 2025 12:20:04 -0400 Subject: [PATCH 17/25] chore(e2e): sync with master --- .../tests/contentSearch/contentData.ts | 42 +- .../contentSearch/contentEditing.spec.ts | 794 ++++++++--------- .../frontend/utils/contentUtils.ts | 831 ++++++++---------- 3 files changed, 743 insertions(+), 924 deletions(-) diff --git a/e2e/dotcms-e2e-node/frontend/tests/contentSearch/contentData.ts b/e2e/dotcms-e2e-node/frontend/tests/contentSearch/contentData.ts index b41f851e054f..37cc56ba3081 100644 --- a/e2e/dotcms-e2e-node/frontend/tests/contentSearch/contentData.ts +++ b/e2e/dotcms-e2e-node/frontend/tests/contentSearch/contentData.ts @@ -1,36 +1,42 @@ + /** * 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", -}; + newTitle : "Automation Test edited", + newBody : "This is a sample content edited" +} /** - * Content actions text content locators - */ +* Content actions text content locators +*/ export const contentProperties = { language: "English (US)", publishWfAction: "Publish", unpublishWfAction: "Unpublish", unlockWfAction: "Unlock", archiveWfAction: "Archive", - deleteWfAction: "Delete", -}; + deleteWfAction: "Delete" +} +/** +* Content to create a file asset +*/ 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", - newFileTextEdited: "Validate you are able to edit text on binary fields", - host: "default", -}; + 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" +} +/** +* Content to create a page asset +*/ export const pageAssetContent = { title: "PageAsset1", host: "default", @@ -39,4 +45,10 @@ export const pageAssetContent = { showOnMenu: true, sortOrder: "1", cacheTTL: 0, -}; +} + +export const accessibilityReport = { + name: 'Content Search' +} + + 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 baa5dd7a280f..3af7adde8dea 100644 --- a/e2e/dotcms-e2e-node/frontend/tests/contentSearch/contentEditing.spec.ts +++ b/e2e/dotcms-e2e-node/frontend/tests/contentSearch/contentEditing.spec.ts @@ -1,142 +1,129 @@ -import { expect, test } from "@playwright/test"; +import {expect, test} from '@playwright/test'; +import {dotCMSUtils, waitForVisibleAndCallback} from '../../utils/dotCMSUtils'; import { - dotCMSUtils, - waitForVisibleAndCallback, -} from "../../utils/dotCMSUtils"; + GroupEntriesLocators, + MenuEntriesLocators, + ToolEntriesLocators +} from '../../locators/navigation/menuLocators'; +import {ContentUtils} from "../../utils/contentUtils"; +import {iFramesLocators, contentGeneric, fileAsset, pageAsset} from "../../locators/globalLocators"; 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, + genericContent1, + contentProperties, + fileAssetContent, + pageAssetContent, + accessibilityReport } from "./contentData"; -import { assert } from "console"; +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(), - async () => {}, - ); - - 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, + title: genericContent1.title, + body: genericContent1.body, + action: 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 and make sure you can discard the changes + */ +test('Edit a generic content and discard changes', async ({page}) => { + const contentUtils = new ContentUtils(page); + const iframe = page.frameLocator(iFramesLocators.main_iframe); + + await contentUtils.selectTypeOnFilter(page, contentGeneric.locator); + await contentUtils.editContent({ + page, + title: genericContent1.title, + newTitle: genericContent1.newTitle, + newBody: "genericContent1", + }); + await waitForVisibleAndCallback(page.getByTestId ('close-button'), () => page.getByTestId('close-button').click()); + await waitForVisibleAndCallback(page.getByRole('button', { name: 'Close' }), () =>page.getByRole('button', { name: 'Close' }).click()); + 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(), - async () => {}, - ); - 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); + + await contentUtils.selectTypeOnFilter(page, contentGeneric.locator); + await contentUtils.editContent({ + page, + title: genericContent1.title, + newTitle: genericContent1.newTitle, + newBody: genericContent1.newBody, + action: 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); - await contentUtils.deleteContent(page, genericContent1.newTitle); -}); +test('Delete a generic of content', async ({ page }) => { + const contentUtils = new ContentUtils(page); + 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, + title: '', + body: genericContent1.body, + action: 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 @@ -157,280 +144,198 @@ test("Validate required on text fields", async ({ page }) => { /** * Test to validate you are able to add file assets importing from url */ -test("Validate adding file assets from URL", async ({ page }) => { - const contentUtils = new ContentUtils(page); - - await contentUtils.addNewContentAction( - page, - fileAsset.locator, - fileAsset.label, - ); - await contentUtils.fillFileAssetForm({ - page, - host: fileAssetContent.host, - title: fileAssetContent.title, - editContent: true, - action: contentProperties.publishWfAction, - fromURL: fileAssetContent.fromURL, - }); - await contentUtils.workflowExecutionValidationAndClose(page, "Content saved"); - await expect( - contentUtils.validateContentExist(page, "DotCMS-logo.svg"), - ).resolves.toBeTruthy(); +test('Validate adding file assets from URL', async ({page}) => { + const contentUtils = new ContentUtils(page); + + await contentUtils.addNewContentAction(page, fileAsset.locator, fileAsset.label); + await contentUtils.fillFileAssetForm({ + page, + host: fileAssetContent.host, + title: fileAssetContent.title, + editContent: true, + action: contentProperties.publishWfAction, + fromURL: fileAssetContent.fromURL + }); + 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 creating a new file */ -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, - ); - await contentUtils.fillFileAssetForm({ - page, - host: fileAssetContent.host, - editContent: false, - title: fileAssetContent.title, - action: contentProperties.publishWfAction, - binaryFileName: fileAssetContent.newFileName, - binaryFileText: fileAssetContent.newFileText, - }); - await contentUtils.workflowExecutionValidationAndClose(page, "Content saved"); - await contentUtils - .validateContentExist(page, fileAssetContent.newFileName) - .then(assert); +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); + await contentUtils.fillFileAssetForm({ + page, + host: fileAssetContent.host, + editContent: false, + title: fileAssetContent.title, + action: contentProperties.publishWfAction, + binaryFileName: fileAssetContent.newFileName, + binaryFileText: fileAssetContent.newFileText + }); + await contentUtils.workflowExecutionValidationAndClose(page, 'Content saved'); + await contentUtils.validateContentExist(page, fileAssetContent.newFileName).then(assert); }); /** * Test to validate you are able to edit file assets text */ -test("Validate you can 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), - ); - - await contentUtils.fillFileAssetForm({ - page, - host: fileAssetContent.host, - editContent: true, - title: fileAssetContent.title, - action: contentProperties.publishWfAction, - binaryFileName: fileAssetContent.newFileName, - binaryFileText: fileAssetContent.newFileTextEdited, - }); - await contentUtils.workflowExecutionValidationAndClose(page, "Content saved"); - - await contentUtils.selectTypeOnFilter(page, fileAsset.locator); - await ( - await contentUtils.getContentElement(page, fileAssetContent.newFileName) - ).click(); - const editIframe = page.frameLocator(iFramesLocators.dot_edit_iframe); - await expect(editIframe.getByRole("code")).toHaveText( - fileAssetContent.newFileTextEdited, - ); +test('Validate you can 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) + ); + + await contentUtils.fillFileAssetForm({ + page, + host: fileAssetContent.host, + editContent: true, + title: fileAssetContent.title, + action: contentProperties.publishWfAction, + binaryFileName: fileAssetContent.newFileName, + binaryFileText: fileAssetContent.newFileTextEdited + }); + await contentUtils.workflowExecutionValidationAndClose(page, 'Content saved'); + + await contentUtils.selectTypeOnFilter(page, fileAsset.locator); + await (await contentUtils.getContentElement(page, fileAssetContent.newFileName)).click(); + const editIframe = page.frameLocator(iFramesLocators.dot_edit_iframe); + await expect(editIframe.getByRole('code')).toHaveText(fileAssetContent.newFileTextEdited); }); /** * Test to validate you are able to remove file assets from the content */ -test("Validate you are able to delete file on binary fields", async ({ - page, -}) => { - const contentUtils = new ContentUtils(page); - const mainFrame = page.frameLocator(iFramesLocators.main_iframe); - - await contentUtils.selectTypeOnFilter(page, fileAsset.locator); - await waitForVisibleAndCallback( - mainFrame.locator("#contentWrapper"), - async () => {}, - ); - 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 detailFrame = page.frameLocator(iFramesLocators.dot_edit_iframe); - await detailFrame.getByRole("button", { name: " Remove" }).click(); - await waitForVisibleAndCallback( - detailFrame.getByTestId("ui-message-icon-container"), - async () => {}, - ); - await detailFrame.getByText("Publish", { exact: true }).click(); - await expect(detailFrame.getByText("The field File Asset is")).toBeVisible(); +test('Validate you are able to delete file on binary fields', async ({page}) => { + const contentUtils = new ContentUtils(page); + const mainFrame = page.frameLocator(iFramesLocators.main_iframe); + + await contentUtils.selectTypeOnFilter(page, fileAsset.locator); + await waitForVisibleAndCallback(mainFrame.locator('#contentWrapper'), async () => {}); + 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 detailFrame = page.frameLocator(iFramesLocators.dot_edit_iframe); + await detailFrame.getByRole('button', { name: ' Remove' }).click(); + await waitForVisibleAndCallback(detailFrame.getByTestId('ui-message-icon-container'), async () => {}); + await detailFrame.getByText('Publish', { exact: true }).click(); + await expect(detailFrame.getByText('The field File Asset is')).toBeVisible(); }); /** * Test to validate the get info on of the binary field on file assets */ -test("Validate file assets show corresponding information", async ({ - page, -}) => { - const contentUtils = new ContentUtils(page); - const mainFrame = page.frameLocator(iFramesLocators.main_iframe); - - await contentUtils.selectTypeOnFilter(page, fileAsset.locator); - await waitForVisibleAndCallback( - mainFrame.locator("#contentWrapper"), - async () => {}, - ); - await ( - await contentUtils.getContentElement(page, fileAssetContent.newFileName) - ).click(); - await waitForVisibleAndCallback(page.getByRole("heading"), () => - expect.soft(page.getByRole("heading")).toContainText(fileAsset.label), - ); - - const detailFrame = page.frameLocator(iFramesLocators.dot_edit_iframe); - await detailFrame.getByTestId("info-btn").click(); - await waitForVisibleAndCallback( - detailFrame.getByText("Bytes"), - async () => {}, - ); - await expect(detailFrame.getByText("Bytes")).toBeVisible(); - await expect(detailFrame.getByTestId("resource-link-FileLink")).toContainText( - "http", - ); - await expect( - detailFrame.getByTestId("resource-link-Resource-Link"), - ).not.toBeEmpty(); - await expect( - detailFrame.getByTestId("resource-link-VersionPath"), - ).not.toBeEmpty(); - await expect(detailFrame.getByTestId("resource-link-IdPath")).not.toBeEmpty(); +test('Validate file assets show corresponding information', async ({page}) => { + const contentUtils = new ContentUtils(page); + const mainFrame = page.frameLocator(iFramesLocators.main_iframe); + + await contentUtils.selectTypeOnFilter(page, fileAsset.locator); + await waitForVisibleAndCallback(mainFrame.locator('#contentWrapper'), async () => {}); + await (await contentUtils.getContentElement(page, fileAssetContent.newFileName)).click(); + await waitForVisibleAndCallback(page.getByRole('heading'), () => + expect.soft(page.getByRole('heading')).toContainText(fileAsset.label) + ); + + const detailFrame = page.frameLocator(iFramesLocators.dot_edit_iframe); + await detailFrame.getByTestId('info-btn').click(); + await waitForVisibleAndCallback(detailFrame.getByText('Bytes'), async () => {}); + await expect(detailFrame.getByText('Bytes')).toBeVisible(); + await expect(detailFrame.getByTestId('resource-link-FileLink')).toContainText("http"); + await expect(detailFrame.getByTestId('resource-link-Resource-Link')).not.toBeEmpty(); + await expect(detailFrame.getByTestId('resource-link-VersionPath')).not.toBeEmpty(); + await expect(detailFrame.getByTestId('resource-link-IdPath')).not.toBeEmpty(); }); //* Test to validate the download of binary fields on file assets -test("Validate the download of binary fields on file assets", async ({ - page, -}) => { - const contentUtils = new ContentUtils(page); - const mainFrame = page.frameLocator(iFramesLocators.main_iframe); - - await contentUtils.selectTypeOnFilter(page, fileAsset.locator); - await waitForVisibleAndCallback( - mainFrame.locator("#contentWrapper"), - async () => {}, - ); - await ( - await contentUtils.getContentElement(page, fileAssetContent.newFileName) - ).click(); - await waitForVisibleAndCallback(page.getByRole("heading"), () => - expect.soft(page.getByRole("heading")).toContainText(fileAsset.label), - ); - const detailFrame = page.frameLocator(iFramesLocators.dot_edit_iframe); - const downloadLink = detailFrame.getByTestId("download-btn"); - await contentUtils.validateDownload(page, downloadLink); +test('Validate the download of binary fields on file assets', async ({page}) => { + const contentUtils = new ContentUtils(page); + const mainFrame = page.frameLocator(iFramesLocators.main_iframe); + + await contentUtils.selectTypeOnFilter(page, fileAsset.locator); + await waitForVisibleAndCallback(mainFrame.locator('#contentWrapper'), async () => {}); + await (await contentUtils.getContentElement(page, fileAssetContent.newFileName)).click(); + await waitForVisibleAndCallback(page.getByRole('heading'), () => + expect.soft(page.getByRole('heading')).toContainText(fileAsset.label) + ); + const detailFrame = page.frameLocator(iFramesLocators.dot_edit_iframe); + const downloadLink = detailFrame.getByTestId('download-btn'); + await contentUtils.validateDownload(page, downloadLink); }); /** * Test to validate the required on file asset fields */ -test("Validate the required on file asset fields", async ({ page }) => { - const contentUtils = new ContentUtils(page); - const detailsFrame = page.frameLocator(iFramesLocators.dot_iframe); - - await contentUtils.addNewContentAction( - page, - fileAsset.locator, - fileAsset.label, - ); - await contentUtils.fillFileAssetForm({ - page, - host: fileAssetContent.host, - editContent: false, - title: fileAssetContent.title, - action: contentProperties.publishWfAction, - }); - await waitForVisibleAndCallback( - detailsFrame.getByText("Error x"), - async () => {}, - ); - 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 contentUtils = new ContentUtils(page); + const detailsFrame = page.frameLocator(iFramesLocators.dot_iframe); + + await contentUtils.addNewContentAction(page, fileAsset.locator, fileAsset.label); + await contentUtils.fillFileAssetForm({ + page, + host: fileAssetContent.host, + editContent: false, + title: fileAssetContent.title, + action: contentProperties.publishWfAction + }); + await waitForVisibleAndCallback(detailsFrame.getByText('Error x'), async () => {}); + const 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 contentUtils = new ContentUtils(page); - const detailsFrame = page.frameLocator(iFramesLocators.dot_iframe); - - await contentUtils.addNewContentAction( - page, - fileAsset.locator, - fileAsset.label, - ); - await detailsFrame.locator("#fileName").fill("test"); - await contentUtils.fillFileAssetForm({ - page, - host: fileAssetContent.host, - editContent: false, - title: fileAssetContent.title, - binaryFileName: fileAssetContent.newFileName, - binaryFileText: fileAssetContent.newFileText, - }); - 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 contentUtils = new ContentUtils(page); + const detailsFrame = page.frameLocator(iFramesLocators.dot_iframe); + + await contentUtils.addNewContentAction(page, fileAsset.locator, fileAsset.label); + await detailsFrame.locator('#fileName').fill('test'); + await contentUtils.fillFileAssetForm({ + page, + host: fileAssetContent.host, + editContent: false, + title: fileAssetContent.title, + binaryFileName: fileAssetContent.newFileName, + binaryFileText: fileAssetContent.newFileText + }); + 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 contentUtils = new ContentUtils(page); - const detailsFrame = page.frameLocator(iFramesLocators.dot_iframe); - - await contentUtils.addNewContentAction( - page, - fileAsset.locator, - fileAsset.label, - ); - await detailsFrame.locator("#fileName").fill("test"); - await contentUtils.fillFileAssetForm({ - page, - host: fileAssetContent.host, - editContent: false, - title: fileAssetContent.title, - binaryFileName: fileAssetContent.newFileName, - binaryFileText: fileAssetContent.newFileText, - }); - const replaceText = detailsFrame.getByText("Do you want to replace the"); - await waitForVisibleAndCallback(replaceText, () => - 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 contentUtils = new ContentUtils(page); + const detailsFrame = page.frameLocator(iFramesLocators.dot_iframe); + + await contentUtils.addNewContentAction(page, fileAsset.locator, fileAsset.label); + await detailsFrame.locator('#fileName').fill('test'); + await contentUtils.fillFileAssetForm({ + page, + host: fileAssetContent.host, + editContent: false, + title: fileAssetContent.title, + binaryFileName: fileAssetContent.newFileName, + binaryFileText: fileAssetContent.newFileText + }); + const replaceText = detailsFrame.getByText('Do you want to replace the'); + await waitForVisibleAndCallback(replaceText, () => expect(replaceText).toBeVisible()); + await detailsFrame.getByLabel('No').click(); + await expect(detailsFrame.locator('#fileName')).toHaveValue('test'); }); /** @@ -452,125 +357,126 @@ test("Validate the auto complete on FileName field rejecting change", async ({ /** * Test to validate you are able to delete a file asset content */ -test("Delete a file asset content", async ({ page }) => { - await new ContentUtils(page).deleteContent(page, fileAssetContent.title); +test('Delete a file asset content', async ({ page }) => { + await new ContentUtils(page).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, - ); - await contentUtils.fillPageAssetForm({ - 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, - }); - const dataFrame = page.frameLocator(iFramesLocators.dataTestId); - await waitForVisibleAndCallback( - dataFrame.getByRole("banner"), - async () => {}, - ); - 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); + await contentUtils.fillPageAssetForm({ + 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, + }); + 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 URL is unique on pages + */ +test('Validate URL is unique on pages', async ({page}) => { + const contentUtils = new ContentUtils(page); + + await contentUtils.addNewContentAction(page, pageAsset.locator, pageAsset.label); + await contentUtils.fillPageAssetForm({ + 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 page.frameLocator('dot-iframe-dialog iframe[name="detailFrame"]').getByText('Another Page with the same').click(); + + const iframe = page.frameLocator(iFramesLocators.dot_iframe); + await expect(iframe.getByText('Another Page with the same')).toBeVisible(); }); /** * 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, - ); - await contentUtils.fillPageAssetForm({ - page, - title: "", - host: pageAssetContent.host, - template: pageAssetContent.template, - showOnMenu: pageAssetContent.showOnMenu, - action: contentProperties.publishWfAction, - }); - 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('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); + await contentUtils.fillPageAssetForm({ + page, + title: "", + host: pageAssetContent.host, + template: pageAssetContent.template, + showOnMenu: pageAssetContent.showOnMenu, + action: contentProperties.publishWfAction + }); + 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, - ); - await contentUtils.fillPageAssetForm({ - page, - title: pageAssetContent.title, - host: pageAssetContent.host, - template: pageAssetContent.template, - showOnMenu: pageAssetContent.showOnMenu, - }); - - 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); + await contentUtils.fillPageAssetForm({ + page, + title: pageAssetContent.title, + host: pageAssetContent.host, + template: pageAssetContent.template, + showOnMenu: pageAssetContent.showOnMenu + }); + + await expect(detailFrame.locator('#url')).toHaveValue(pageAssetContent.title.toLowerCase()); + await expect(detailFrame.locator('#friendlyName')).toHaveValue(pageAssetContent.title); }); /** * Test to validate you are able to unpublish a page asset */ -test("Validate you are able to unpublish pages", async ({ page }) => { - const contentUtils = new ContentUtils(page); - await contentUtils.selectTypeOnFilter(page, pageAsset.locator); - await contentUtils.performWorkflowAction( - page, - pageAssetContent.title, - contentProperties.unpublishWfAction, - ); - await contentUtils.getContentState(page, pageAssetContent.title).then(assert); +test('Validate you are able to unpublish pages', async ({page}) => { + const contentUtils = new ContentUtils(page); + await contentUtils.selectTypeOnFilter(page, pageAsset.locator); + await contentUtils.performWorkflowAction(page, pageAssetContent.title, contentProperties.unpublishWfAction); + await contentUtils.getContentState(page, pageAssetContent.title).then(assert); }); /** * Test to validate you are able to delete pages */ -test("Validate you are able to delete pages", async ({ page }) => { - const contentUtils = new ContentUtils(page); - await contentUtils.selectTypeOnFilter(page, pageAsset.locator); - await contentUtils.deleteContent(page, pageAssetContent.title); +test('Validate you are able to delete pages', async ({page}) => { + const contentUtils = new ContentUtils(page); + await contentUtils.selectTypeOnFilter(page, pageAsset.locator); + await contentUtils.deleteContent(page, pageAssetContent.title); +}); + +/** + * Test to do a regression according the axe-standards on accessibility + */ +/** +test('accessibility test', async ({page}) => { + const accessibility = new accessibilityUtils(page); + const accessibilityScanResults = await accessibility.generateReport(page, accessibilityReport.name); + expect(accessibilityScanResults.violations).toEqual([]); // 5 }); + */ \ 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 009dd0b77ece..52579fef5c97 100644 --- a/e2e/dotcms-e2e-node/frontend/utils/contentUtils.ts +++ b/e2e/dotcms-e2e-node/frontend/utils/contentUtils.ts @@ -1,505 +1,406 @@ -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"; +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, - 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 () => {}, + page: Page; + + constructor(page: Page) { + this.page = page; + } + + /** + * Fill the rich text form + * @param params + */ + async fillRichTextForm(params: RichTextFormParams) { + const {page, title, body, action, newBody, newTitle} = params; + const dotIframe = page.frameLocator(iFramesLocators.dot_iframe); + + await waitForVisibleAndCallback(page.getByRole('heading'), () => + expect.soft(page.getByRole('heading')).toContainText(contentGeneric.label) ); - const editor = editFrame.getByLabel("Editor content;Press Alt+F1"); - await editor.click(); // Focus on the editor - await page.keyboard.press("Control+A"); // Select all text (Cmd+A for Mac) - await page.keyboard.press("Backspace"); - 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); + + if (newTitle) { + await dotIframe.locator('#title').clear(); + await dotIframe.locator('#title').fill(newTitle); + } + if (newBody) { + await dotIframe.locator('#block-editor-body div').nth(1).clear() + await dotIframe.locator('#block-editor-body div').nth(1).fill(newBody); + } + if (!newTitle && !newBody) { + await dotIframe.locator('#title').fill(title); + await dotIframe.locator('#block-editor-body div').nth(1).fill(body); + } + 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 () => { + }); + const editor = editFrame.getByLabel('Editor content;Press Alt+F1'); + await editor.click(); // Focus on the editor + await page.keyboard.press('Control+A'); // Select all text (Cmd+A for Mac) + await page.keyboard.press('Backspace'); + 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 () => { + await 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); }); - 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 (action) { + await dotIframe.getByText(action).first().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 () => {}, - ); + /** + * 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()); } - await waitForVisibleAndCallback(dotIframe.locator("#title"), async () => { - await dotIframe.locator("#title").fill(title); - }); + /** + * 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()); + + await waitForVisibleAndCallback(iframe.getByLabel('structure_inode_popup'), async () => {}); + + const typeLocatorByText = iframe.getByText(typeLocator); + await waitForVisibleAndCallback(typeLocatorByText, () => typeLocatorByText.click()); + } - if (action) { - await dotIframe.getByText(action).first().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 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"), - 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(); - } - - /** - * 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)"), - async () => {}, - ); - 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 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); + + 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; } - 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(); - } + /** + * 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; } - 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; + + + /** + * Edit content on the content portlet + * @param params + */ + async editContent(params: RichTextFormParams) { + const {page, title, action} = params; + const contentElement = await this.getContentElement(page, title); + if (!contentElement) { + console.log('Content not found'); + return; + } + await contentElement.click(); + await this.fillRichTextForm(params); + if(action) { + await this.workflowExecutionValidationAndClose(page, 'Content saved'); + } } - 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 waitForVisibleAndCallback( - iframe.locator("#contentWrapper"), - async () => {}, - ); - } else if (contentState === "archived") { - await this.performWorkflowAction( - page, - title, - contentProperties.deleteWfAction, - ); - return; - } - await page.waitForLoadState(); + /** + * 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 waitForVisibleAndCallback(iframe.locator('#contentWrapper'), async () => {}); + } else if (contentState === 'archived') { + await this.performWorkflowAction(page, title, contentProperties.deleteWfAction); + return; + } + + await page.waitForLoadState(); + } } - } - - /** - * 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", - }); + + /** + * 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()); } - 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(), - ); - } - - /** - * 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"); - } + + /** + * 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; } - 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(); + + /** + * 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 (template) { - await dotIframe.locator("#widget_templateSel div").first().click(); - await dotIframe.getByText(template).click(); + + + /** + * Validate the download of a file + * @param page + * @param downloadTriggerSelector + */ + async validateDownload(page: Page, downloadTriggerSelector: Locator) { + // Start waiting for the download event + const downloadPromise = page.waitForEvent('download'); + + // Trigger the download + await downloadTriggerSelector.click(); + + // Wait for the download to complete + const download = await downloadPromise; + + // Assert the download was successful + const fileName = download.suggestedFilename(); + console.log(`Downloaded file: ${fileName}`); + expect(fileName).toBeTruthy(); } - 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(); - } - - /** - * Validate the download of a file - * @param page - * @param downloadTriggerSelector - */ - async validateDownload(page: Page, downloadTriggerSelector: Locator) { - // Start waiting for the download event - const downloadPromise = page.waitForEvent("download"); - - // Trigger the download - await downloadTriggerSelector.click(); - - // Wait for the download to complete - const download = await downloadPromise; - - // Assert the download was successful - const fileName = download.suggestedFilename(); - console.log(`Downloaded file: ${fileName}`); - expect(fileName).toBeTruthy(); - } } /** * Base form params */ interface BaseFormParams { - page: Page; - title: string; - action?: string; + page: Page; + title: string; + action?: string; } + +interface RichTextFormParams extends BaseFormParams { + body?: string, + action?: string + newTitle?: string, + newBody?: string, +} /** * Parameter to fill the file asset form params */ interface FileAssetFormParams extends BaseFormParams { - host: string; - editContent: boolean; - fileName?: string; - fromURL?: string; - binaryFileName?: string; - binaryFileText?: 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; } + + + From 7ef4013fb484770e1a8781b92350eecadbc2350a Mon Sep 17 00:00:00 2001 From: Nicolas Molina Date: Thu, 16 Jan 2025 12:30:48 -0400 Subject: [PATCH 18/25] chore(e2e): docs --- e2e/dotcms-e2e-node/README.md | 8 +++----- 1 file changed, 3 insertions(+), 5 deletions(-) diff --git a/e2e/dotcms-e2e-node/README.md b/e2e/dotcms-e2e-node/README.md index 3d369f491cc9..397023d04158 100644 --- a/e2e/dotcms-e2e-node/README.md +++ b/e2e/dotcms-e2e-node/README.md @@ -79,7 +79,7 @@ Now that we have these packages installed we need to start dotCMS (with its depe At `e2e/e2e-dotcms-node/frontend/package.json` you can find the available scripts you can call to execute the tests: -```json lines +```json "scripts": { "show-report": "if [[ \"$CURRENT_ENV\" != \"ci\" ]]; then fi", "start": "PLAYWRIGHT_JUNIT_SUITE_ID=nodee2etestsuite PLAYWRIGHT_JUNIT_SUITE_NAME='E2E Node Test Suite' PLAYWRIGHT_JUNIT_OUTPUT_FILE='../target/failsafe-reports/TEST-e2e-node-results.xml' yarn playwright test ${PLAYWRIGHT_SPECIFIC} ${PLAYWRIGHT_DEBUG}; yarn run show-report", @@ -89,12 +89,10 @@ At `e2e/e2e-dotcms-node/frontend/package.json` you can find the available script "post-testing": "PLAYWRIGHT_JUNIT_OUTPUT_FILE='../target/failsafe-reports/TEST-e2e-node-results.xml' node index.js" } ``` + All these scripts assume that there is a dotCMS instance running at `8080` port. - `start-local`: runs E2E tests against http://localhost:8080 -- `start-dev`: runs E2E tests against http://localhost:4200, that means it runs a -```shell -nx serve dotcms-ui -``` +- `start-dev`: runs E2E tests against http://localhost:4200, that means it runs a `nx serve dotcms-ui` command before the tests on top of what is found on http://localhost:8080 - `start-ci`: runs E2E tests against http://localhost:8080 in `headless` mode which is how it's done in the pipeline From 0108942031b5344205a74c5c0e3f6a09dd22ea66 Mon Sep 17 00:00:00 2001 From: Nicolas Molina Date: Thu, 16 Jan 2025 12:36:08 -0400 Subject: [PATCH 19/25] chore(e2e): sync with master --- .../frontend/locators/globalLocators.ts | 50 +- .../locators/navigation/menuLocators.ts | 64 +- .../tests/contentSearch/contentData.ts | 64 +- .../contentSearch/contentEditing.spec.ts | 824 +++++++---------- .../contentSearch/portletIntegrity.spec.ts | 22 +- .../frontend/tests/login/credentialsData.ts | 2 +- .../frontend/tests/login/login.spec.ts | 60 +- .../frontend/tests/login/translations.spec.ts | 2 +- .../frontend/utils/accessibilityUtils.ts | 48 +- .../frontend/utils/contentUtils.ts | 831 ++++++++---------- .../frontend/utils/dotCMSUtils.ts | 101 +-- e2e/dotcms-e2e-node/frontend/yarn.lock | 20 +- 12 files changed, 898 insertions(+), 1190 deletions(-) diff --git a/e2e/dotcms-e2e-node/frontend/locators/globalLocators.ts b/e2e/dotcms-e2e-node/frontend/locators/globalLocators.ts index 1aa3fb4c485b..5cfcacb9ba83 100644 --- a/e2e/dotcms-e2e-node/frontend/locators/globalLocators.ts +++ b/e2e/dotcms-e2e-node/frontend/locators/globalLocators.ts @@ -2,48 +2,48 @@ * 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"]', - dot_edit_iframe: 'dot-edit-contentlet iframe[name="detailFrame"]', -}; + 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 79e4992638bc..cda521ff3009 100644 --- a/e2e/dotcms-e2e-node/frontend/locators/navigation/menuLocators.ts +++ b/e2e/dotcms-e2e-node/frontend/locators/navigation/menuLocators.ts @@ -1,48 +1,44 @@ -import { Page, Locator } from "@playwright/test"; +import {Page, Locator} from '@playwright/test'; export class GroupEntriesLocators { - readonly SITE: Locator; - readonly CONTENT: Locator; - readonly CONTENT_MODEL: Locator; - readonly SCHEMA: Locator; - - constructor(page: Page) { - this.SITE = page.getByText("Site", { exact: true }); - this.CONTENT = page - .getByRole("complementary") - .getByText("Content", { exact: true }); - - this.CONTENT_MODEL = page.getByText("Content Model"); - 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; - - constructor(page: Page) { - this.EXPAND = page.getByRole("button", { name: "" }); - this.COLLAPSE = page - .locator('button[ng-reflect-ng-class="[object Object]"]') - .first(); - } -} + readonly EXPAND: Locator; + readonly COLLAPSE: Locator; + + constructor(page: Page) { + 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 d678a7d84a05..b907eb366b6f 100644 --- a/e2e/dotcms-e2e-node/frontend/tests/contentSearch/contentData.ts +++ b/e2e/dotcms-e2e-node/frontend/tests/contentSearch/contentData.ts @@ -1,52 +1,54 @@ + /** * 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" +} /** * Content to create a file asset */ 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", - newFileTextEdited: "Validate you are able to edit text on binary fields", - 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" +} /** * Content to create a page asset */ 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, +} export const accessibilityReport = { - name: "Content Search", -}; + name: 'Content Search' +} + + 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 43082ff54aad..3af7adde8dea 100644 --- a/e2e/dotcms-e2e-node/frontend/tests/contentSearch/contentEditing.spec.ts +++ b/e2e/dotcms-e2e-node/frontend/tests/contentSearch/contentEditing.spec.ts @@ -1,170 +1,129 @@ -import { expect, test } from "@playwright/test"; +import {expect, test} from '@playwright/test'; +import {dotCMSUtils, waitForVisibleAndCallback} from '../../utils/dotCMSUtils'; import { - dotCMSUtils, - waitForVisibleAndCallback, -} from "../../utils/dotCMSUtils"; + GroupEntriesLocators, + MenuEntriesLocators, + ToolEntriesLocators +} from '../../locators/navigation/menuLocators'; +import {ContentUtils} from "../../utils/contentUtils"; +import {iFramesLocators, contentGeneric, fileAsset, pageAsset} from "../../locators/globalLocators"; 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, - accessibilityReport, + genericContent1, + contentProperties, + fileAssetContent, + pageAssetContent, + accessibilityReport } from "./contentData"; -import { assert } from "console"; +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, - title: genericContent1.title, - body: genericContent1.body, - action: 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('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, + title: genericContent1.title, + body: genericContent1.body, + action: 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 and make sure you can discard the changes */ -test("Edit a generic content and discard changes", async ({ page }) => { - const contentUtils = new ContentUtils(page); - const iframe = page.frameLocator(iFramesLocators.main_iframe); - - await contentUtils.selectTypeOnFilter(page, contentGeneric.locator); - await contentUtils.editContent({ - page, - title: genericContent1.title, - newTitle: genericContent1.newTitle, - newBody: "genericContent1", - }); - await waitForVisibleAndCallback(page.getByTestId("close-button"), () => - page.getByTestId("close-button").click(), - ); - await waitForVisibleAndCallback( - page.getByRole("button", { name: "Close" }), - () => page.getByRole("button", { name: "Close" }).click(), - ); - await waitForVisibleAndCallback( - iframe.locator("#results_table tbody tr").first(), - async () => {}, - ); - await contentUtils - .validateContentExist(page, genericContent1.title) - .then(assert); +test('Edit a generic content and discard changes', async ({page}) => { + const contentUtils = new ContentUtils(page); + const iframe = page.frameLocator(iFramesLocators.main_iframe); + + await contentUtils.selectTypeOnFilter(page, contentGeneric.locator); + await contentUtils.editContent({ + page, + title: genericContent1.title, + newTitle: genericContent1.newTitle, + newBody: "genericContent1", + }); + await waitForVisibleAndCallback(page.getByTestId ('close-button'), () => page.getByTestId('close-button').click()); + await waitForVisibleAndCallback(page.getByRole('button', { name: 'Close' }), () =>page.getByRole('button', { name: 'Close' }).click()); + 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); - - await contentUtils.selectTypeOnFilter(page, contentGeneric.locator); - await contentUtils.editContent({ - page, - title: genericContent1.title, - newTitle: genericContent1.newTitle, - newBody: genericContent1.newBody, - action: contentProperties.publishWfAction, - }); - await waitForVisibleAndCallback( - iframe.locator("#results_table tbody tr").first(), - async () => {}, - ); - 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); + + await contentUtils.selectTypeOnFilter(page, contentGeneric.locator); + await contentUtils.editContent({ + page, + title: genericContent1.title, + newTitle: genericContent1.newTitle, + newBody: genericContent1.newBody, + action: 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); - await contentUtils.deleteContent(page, genericContent1.newTitle); -}); +test('Delete a generic of content', async ({ page }) => { + const contentUtils = new ContentUtils(page); + 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, - title: "", - body: genericContent1.body, - action: 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, + title: '', + body: genericContent1.body, + action: 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 @@ -185,280 +144,198 @@ test("Validate required on text fields", async ({ page }) => { /** * Test to validate you are able to add file assets importing from url */ -test("Validate adding file assets from URL", async ({ page }) => { - const contentUtils = new ContentUtils(page); - - await contentUtils.addNewContentAction( - page, - fileAsset.locator, - fileAsset.label, - ); - await contentUtils.fillFileAssetForm({ - page, - host: fileAssetContent.host, - title: fileAssetContent.title, - editContent: true, - action: contentProperties.publishWfAction, - fromURL: fileAssetContent.fromURL, - }); - await contentUtils.workflowExecutionValidationAndClose(page, "Content saved"); - await expect( - contentUtils.validateContentExist(page, "DotCMS-logo.svg"), - ).resolves.toBeTruthy(); +test('Validate adding file assets from URL', async ({page}) => { + const contentUtils = new ContentUtils(page); + + await contentUtils.addNewContentAction(page, fileAsset.locator, fileAsset.label); + await contentUtils.fillFileAssetForm({ + page, + host: fileAssetContent.host, + title: fileAssetContent.title, + editContent: true, + action: contentProperties.publishWfAction, + fromURL: fileAssetContent.fromURL + }); + 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 creating a new file */ -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, - ); - await contentUtils.fillFileAssetForm({ - page, - host: fileAssetContent.host, - editContent: false, - title: fileAssetContent.title, - action: contentProperties.publishWfAction, - binaryFileName: fileAssetContent.newFileName, - binaryFileText: fileAssetContent.newFileText, - }); - await contentUtils.workflowExecutionValidationAndClose(page, "Content saved"); - await contentUtils - .validateContentExist(page, fileAssetContent.newFileName) - .then(assert); +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); + await contentUtils.fillFileAssetForm({ + page, + host: fileAssetContent.host, + editContent: false, + title: fileAssetContent.title, + action: contentProperties.publishWfAction, + binaryFileName: fileAssetContent.newFileName, + binaryFileText: fileAssetContent.newFileText + }); + await contentUtils.workflowExecutionValidationAndClose(page, 'Content saved'); + await contentUtils.validateContentExist(page, fileAssetContent.newFileName).then(assert); }); /** * Test to validate you are able to edit file assets text */ -test("Validate you can 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), - ); - - await contentUtils.fillFileAssetForm({ - page, - host: fileAssetContent.host, - editContent: true, - title: fileAssetContent.title, - action: contentProperties.publishWfAction, - binaryFileName: fileAssetContent.newFileName, - binaryFileText: fileAssetContent.newFileTextEdited, - }); - await contentUtils.workflowExecutionValidationAndClose(page, "Content saved"); - - await contentUtils.selectTypeOnFilter(page, fileAsset.locator); - await ( - await contentUtils.getContentElement(page, fileAssetContent.newFileName) - ).click(); - const editIframe = page.frameLocator(iFramesLocators.dot_edit_iframe); - await expect(editIframe.getByRole("code")).toHaveText( - fileAssetContent.newFileTextEdited, - ); +test('Validate you can 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) + ); + + await contentUtils.fillFileAssetForm({ + page, + host: fileAssetContent.host, + editContent: true, + title: fileAssetContent.title, + action: contentProperties.publishWfAction, + binaryFileName: fileAssetContent.newFileName, + binaryFileText: fileAssetContent.newFileTextEdited + }); + await contentUtils.workflowExecutionValidationAndClose(page, 'Content saved'); + + await contentUtils.selectTypeOnFilter(page, fileAsset.locator); + await (await contentUtils.getContentElement(page, fileAssetContent.newFileName)).click(); + const editIframe = page.frameLocator(iFramesLocators.dot_edit_iframe); + await expect(editIframe.getByRole('code')).toHaveText(fileAssetContent.newFileTextEdited); }); /** * Test to validate you are able to remove file assets from the content */ -test("Validate you are able to delete file on binary fields", async ({ - page, -}) => { - const contentUtils = new ContentUtils(page); - const mainFrame = page.frameLocator(iFramesLocators.main_iframe); - - await contentUtils.selectTypeOnFilter(page, fileAsset.locator); - await waitForVisibleAndCallback( - mainFrame.locator("#contentWrapper"), - async () => {}, - ); - 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 detailFrame = page.frameLocator(iFramesLocators.dot_edit_iframe); - await detailFrame.getByRole("button", { name: " Remove" }).click(); - await waitForVisibleAndCallback( - detailFrame.getByTestId("ui-message-icon-container"), - async () => {}, - ); - await detailFrame.getByText("Publish", { exact: true }).click(); - await expect(detailFrame.getByText("The field File Asset is")).toBeVisible(); +test('Validate you are able to delete file on binary fields', async ({page}) => { + const contentUtils = new ContentUtils(page); + const mainFrame = page.frameLocator(iFramesLocators.main_iframe); + + await contentUtils.selectTypeOnFilter(page, fileAsset.locator); + await waitForVisibleAndCallback(mainFrame.locator('#contentWrapper'), async () => {}); + 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 detailFrame = page.frameLocator(iFramesLocators.dot_edit_iframe); + await detailFrame.getByRole('button', { name: ' Remove' }).click(); + await waitForVisibleAndCallback(detailFrame.getByTestId('ui-message-icon-container'), async () => {}); + await detailFrame.getByText('Publish', { exact: true }).click(); + await expect(detailFrame.getByText('The field File Asset is')).toBeVisible(); }); /** * Test to validate the get info on of the binary field on file assets */ -test("Validate file assets show corresponding information", async ({ - page, -}) => { - const contentUtils = new ContentUtils(page); - const mainFrame = page.frameLocator(iFramesLocators.main_iframe); - - await contentUtils.selectTypeOnFilter(page, fileAsset.locator); - await waitForVisibleAndCallback( - mainFrame.locator("#contentWrapper"), - async () => {}, - ); - await ( - await contentUtils.getContentElement(page, fileAssetContent.newFileName) - ).click(); - await waitForVisibleAndCallback(page.getByRole("heading"), () => - expect.soft(page.getByRole("heading")).toContainText(fileAsset.label), - ); - - const detailFrame = page.frameLocator(iFramesLocators.dot_edit_iframe); - await detailFrame.getByTestId("info-btn").click(); - await waitForVisibleAndCallback( - detailFrame.getByText("Bytes"), - async () => {}, - ); - await expect(detailFrame.getByText("Bytes")).toBeVisible(); - await expect(detailFrame.getByTestId("resource-link-FileLink")).toContainText( - "http", - ); - await expect( - detailFrame.getByTestId("resource-link-Resource-Link"), - ).not.toBeEmpty(); - await expect( - detailFrame.getByTestId("resource-link-VersionPath"), - ).not.toBeEmpty(); - await expect(detailFrame.getByTestId("resource-link-IdPath")).not.toBeEmpty(); +test('Validate file assets show corresponding information', async ({page}) => { + const contentUtils = new ContentUtils(page); + const mainFrame = page.frameLocator(iFramesLocators.main_iframe); + + await contentUtils.selectTypeOnFilter(page, fileAsset.locator); + await waitForVisibleAndCallback(mainFrame.locator('#contentWrapper'), async () => {}); + await (await contentUtils.getContentElement(page, fileAssetContent.newFileName)).click(); + await waitForVisibleAndCallback(page.getByRole('heading'), () => + expect.soft(page.getByRole('heading')).toContainText(fileAsset.label) + ); + + const detailFrame = page.frameLocator(iFramesLocators.dot_edit_iframe); + await detailFrame.getByTestId('info-btn').click(); + await waitForVisibleAndCallback(detailFrame.getByText('Bytes'), async () => {}); + await expect(detailFrame.getByText('Bytes')).toBeVisible(); + await expect(detailFrame.getByTestId('resource-link-FileLink')).toContainText("http"); + await expect(detailFrame.getByTestId('resource-link-Resource-Link')).not.toBeEmpty(); + await expect(detailFrame.getByTestId('resource-link-VersionPath')).not.toBeEmpty(); + await expect(detailFrame.getByTestId('resource-link-IdPath')).not.toBeEmpty(); }); //* Test to validate the download of binary fields on file assets -test("Validate the download of binary fields on file assets", async ({ - page, -}) => { - const contentUtils = new ContentUtils(page); - const mainFrame = page.frameLocator(iFramesLocators.main_iframe); - - await contentUtils.selectTypeOnFilter(page, fileAsset.locator); - await waitForVisibleAndCallback( - mainFrame.locator("#contentWrapper"), - async () => {}, - ); - await ( - await contentUtils.getContentElement(page, fileAssetContent.newFileName) - ).click(); - await waitForVisibleAndCallback(page.getByRole("heading"), () => - expect.soft(page.getByRole("heading")).toContainText(fileAsset.label), - ); - const detailFrame = page.frameLocator(iFramesLocators.dot_edit_iframe); - const downloadLink = detailFrame.getByTestId("download-btn"); - await contentUtils.validateDownload(page, downloadLink); +test('Validate the download of binary fields on file assets', async ({page}) => { + const contentUtils = new ContentUtils(page); + const mainFrame = page.frameLocator(iFramesLocators.main_iframe); + + await contentUtils.selectTypeOnFilter(page, fileAsset.locator); + await waitForVisibleAndCallback(mainFrame.locator('#contentWrapper'), async () => {}); + await (await contentUtils.getContentElement(page, fileAssetContent.newFileName)).click(); + await waitForVisibleAndCallback(page.getByRole('heading'), () => + expect.soft(page.getByRole('heading')).toContainText(fileAsset.label) + ); + const detailFrame = page.frameLocator(iFramesLocators.dot_edit_iframe); + const downloadLink = detailFrame.getByTestId('download-btn'); + await contentUtils.validateDownload(page, downloadLink); }); /** * Test to validate the required on file asset fields */ -test("Validate the required on file asset fields", async ({ page }) => { - const contentUtils = new ContentUtils(page); - const detailsFrame = page.frameLocator(iFramesLocators.dot_iframe); - - await contentUtils.addNewContentAction( - page, - fileAsset.locator, - fileAsset.label, - ); - await contentUtils.fillFileAssetForm({ - page, - host: fileAssetContent.host, - editContent: false, - title: fileAssetContent.title, - action: contentProperties.publishWfAction, - }); - await waitForVisibleAndCallback( - detailsFrame.getByText("Error x"), - async () => {}, - ); - 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 contentUtils = new ContentUtils(page); + const detailsFrame = page.frameLocator(iFramesLocators.dot_iframe); + + await contentUtils.addNewContentAction(page, fileAsset.locator, fileAsset.label); + await contentUtils.fillFileAssetForm({ + page, + host: fileAssetContent.host, + editContent: false, + title: fileAssetContent.title, + action: contentProperties.publishWfAction + }); + await waitForVisibleAndCallback(detailsFrame.getByText('Error x'), async () => {}); + const 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 contentUtils = new ContentUtils(page); - const detailsFrame = page.frameLocator(iFramesLocators.dot_iframe); - - await contentUtils.addNewContentAction( - page, - fileAsset.locator, - fileAsset.label, - ); - await detailsFrame.locator("#fileName").fill("test"); - await contentUtils.fillFileAssetForm({ - page, - host: fileAssetContent.host, - editContent: false, - title: fileAssetContent.title, - binaryFileName: fileAssetContent.newFileName, - binaryFileText: fileAssetContent.newFileText, - }); - 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 contentUtils = new ContentUtils(page); + const detailsFrame = page.frameLocator(iFramesLocators.dot_iframe); + + await contentUtils.addNewContentAction(page, fileAsset.locator, fileAsset.label); + await detailsFrame.locator('#fileName').fill('test'); + await contentUtils.fillFileAssetForm({ + page, + host: fileAssetContent.host, + editContent: false, + title: fileAssetContent.title, + binaryFileName: fileAssetContent.newFileName, + binaryFileText: fileAssetContent.newFileText + }); + 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 contentUtils = new ContentUtils(page); - const detailsFrame = page.frameLocator(iFramesLocators.dot_iframe); - - await contentUtils.addNewContentAction( - page, - fileAsset.locator, - fileAsset.label, - ); - await detailsFrame.locator("#fileName").fill("test"); - await contentUtils.fillFileAssetForm({ - page, - host: fileAssetContent.host, - editContent: false, - title: fileAssetContent.title, - binaryFileName: fileAssetContent.newFileName, - binaryFileText: fileAssetContent.newFileText, - }); - const replaceText = detailsFrame.getByText("Do you want to replace the"); - await waitForVisibleAndCallback(replaceText, () => - 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 contentUtils = new ContentUtils(page); + const detailsFrame = page.frameLocator(iFramesLocators.dot_iframe); + + await contentUtils.addNewContentAction(page, fileAsset.locator, fileAsset.label); + await detailsFrame.locator('#fileName').fill('test'); + await contentUtils.fillFileAssetForm({ + page, + host: fileAssetContent.host, + editContent: false, + title: fileAssetContent.title, + binaryFileName: fileAssetContent.newFileName, + binaryFileText: fileAssetContent.newFileText + }); + const replaceText = detailsFrame.getByText('Do you want to replace the'); + await waitForVisibleAndCallback(replaceText, () => expect(replaceText).toBeVisible()); + await detailsFrame.getByLabel('No').click(); + await expect(detailsFrame.locator('#fileName')).toHaveValue('test'); }); /** @@ -480,158 +357,117 @@ test("Validate the auto complete on FileName field rejecting change", async ({ /** * Test to validate you are able to delete a file asset content */ -test("Delete a file asset content", async ({ page }) => { - await new ContentUtils(page).deleteContent(page, fileAssetContent.title); +test('Delete a file asset content', async ({ page }) => { + await new ContentUtils(page).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, - ); - await contentUtils.fillPageAssetForm({ - 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, - }); - const dataFrame = page.frameLocator(iFramesLocators.dataTestId); - await waitForVisibleAndCallback( - dataFrame.getByRole("banner"), - async () => {}, - ); - 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); + await contentUtils.fillPageAssetForm({ + 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, + }); + 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 URL is unique on pages */ -test("Validate URL is unique on pages", async ({ page }) => { - const contentUtils = new ContentUtils(page); - - await contentUtils.addNewContentAction( - page, - pageAsset.locator, - pageAsset.label, - ); - await contentUtils.fillPageAssetForm({ - 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 page - .frameLocator('dot-iframe-dialog iframe[name="detailFrame"]') - .getByText("Another Page with the same") - .click(); - - const iframe = page.frameLocator(iFramesLocators.dot_iframe); - await expect(iframe.getByText("Another Page with the same")).toBeVisible(); +test('Validate URL is unique on pages', async ({page}) => { + const contentUtils = new ContentUtils(page); + + await contentUtils.addNewContentAction(page, pageAsset.locator, pageAsset.label); + await contentUtils.fillPageAssetForm({ + 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 page.frameLocator('dot-iframe-dialog iframe[name="detailFrame"]').getByText('Another Page with the same').click(); + + const iframe = page.frameLocator(iFramesLocators.dot_iframe); + await expect(iframe.getByText('Another Page with the same')).toBeVisible(); }); /** * 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, - ); - await contentUtils.fillPageAssetForm({ - page, - title: "", - host: pageAssetContent.host, - template: pageAssetContent.template, - showOnMenu: pageAssetContent.showOnMenu, - action: contentProperties.publishWfAction, - }); - 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('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); + await contentUtils.fillPageAssetForm({ + page, + title: "", + host: pageAssetContent.host, + template: pageAssetContent.template, + showOnMenu: pageAssetContent.showOnMenu, + action: contentProperties.publishWfAction + }); + 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, - ); - await contentUtils.fillPageAssetForm({ - page, - title: pageAssetContent.title, - host: pageAssetContent.host, - template: pageAssetContent.template, - showOnMenu: pageAssetContent.showOnMenu, - }); - - 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); + await contentUtils.fillPageAssetForm({ + page, + title: pageAssetContent.title, + host: pageAssetContent.host, + template: pageAssetContent.template, + showOnMenu: pageAssetContent.showOnMenu + }); + + await expect(detailFrame.locator('#url')).toHaveValue(pageAssetContent.title.toLowerCase()); + await expect(detailFrame.locator('#friendlyName')).toHaveValue(pageAssetContent.title); }); /** * Test to validate you are able to unpublish a page asset */ -test("Validate you are able to unpublish pages", async ({ page }) => { - const contentUtils = new ContentUtils(page); - await contentUtils.selectTypeOnFilter(page, pageAsset.locator); - await contentUtils.performWorkflowAction( - page, - pageAssetContent.title, - contentProperties.unpublishWfAction, - ); - await contentUtils.getContentState(page, pageAssetContent.title).then(assert); +test('Validate you are able to unpublish pages', async ({page}) => { + const contentUtils = new ContentUtils(page); + await contentUtils.selectTypeOnFilter(page, pageAsset.locator); + await contentUtils.performWorkflowAction(page, pageAssetContent.title, contentProperties.unpublishWfAction); + await contentUtils.getContentState(page, pageAssetContent.title).then(assert); }); /** * Test to validate you are able to delete pages */ -test("Validate you are able to delete pages", async ({ page }) => { - const contentUtils = new ContentUtils(page); - await contentUtils.selectTypeOnFilter(page, pageAsset.locator); - await contentUtils.deleteContent(page, pageAssetContent.title); +test('Validate you are able to delete pages', async ({page}) => { + const contentUtils = new ContentUtils(page); + await contentUtils.selectTypeOnFilter(page, pageAsset.locator); + await contentUtils.deleteContent(page, pageAssetContent.title); }); /** @@ -643,4 +479,4 @@ test('accessibility test', async ({page}) => { const accessibilityScanResults = await accessibility.generateReport(page, accessibilityReport.name); expect(accessibilityScanResults.violations).toEqual([]); // 5 }); - */ + */ \ 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 40eafaa6c148..4ff6e62d0db2 100644 --- a/e2e/dotcms-e2e-node/frontend/tests/contentSearch/portletIntegrity.spec.ts +++ b/e2e/dotcms-e2e-node/frontend/tests/contentSearch/portletIntegrity.spec.ts @@ -72,7 +72,7 @@ test("Search filter", async ({ page }) => { await contentUtils.fillRichTextForm({ page, title: genericContent1.title, - body: genericContent1.body, + body: genericContent1.body, action: contentProperties.publishWfAction, }); await contentUtils.workflowExecutionValidationAndClose(page, "Content saved"); @@ -224,14 +224,14 @@ test("Validate the clear button in the search filter", async ({ page }) => { // Validate the search filter has been cleared await expect( - iframe.locator('input[name="scheme_id_select"]'), + iframe + .locator('input[name="scheme_id_select"]') + , ).toHaveAttribute("value", "catchall"); - await expect(iframe.locator('input[name="step_id_select"]')).toHaveAttribute( - "value", - "catchall", - ); - await expect(iframe.locator("#showingSelect")).toHaveAttribute( - "value", + await expect( + iframe.locator('input[name="step_id_select"]'), + ).toHaveAttribute("value", "catchall"); + await expect(iframe.locator("#showingSelect")).toHaveAttribute("value", "All", ); }); @@ -250,7 +250,9 @@ test("Validate the hide button collapse the filter", async ({ page }) => { ); await page.waitForTimeout(1000); - await expect(iframe.getByRole("link", { name: "Advanced" })).toBeHidden(); + await expect( + iframe.getByRole("link", { name: "Advanced" }), + ).toBeHidden(); // Click on the hide button await iframe.getByRole("link", { name: "Hide" }).click(); @@ -258,4 +260,4 @@ test("Validate the hide button collapse the filter", async ({ page }) => { // Validate the filter has been collapsed await expect(iframe.getByRole("link", { name: "Advanced" })).toBeVisible(); -}); +}); \ No newline at end of file diff --git a/e2e/dotcms-e2e-node/frontend/tests/login/credentialsData.ts b/e2e/dotcms-e2e-node/frontend/tests/login/credentialsData.ts index 444ffe912a36..d4ee12051553 100644 --- a/e2e/dotcms-e2e-node/frontend/tests/login/credentialsData.ts +++ b/e2e/dotcms-e2e-node/frontend/tests/login/credentialsData.ts @@ -28,4 +28,4 @@ export const wrong1 = { export const wrong2 = { username: "chris2@dotcms.com", password: "chris", -}; +}; \ No newline at end of file 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/tests/login/translations.spec.ts b/e2e/dotcms-e2e-node/frontend/tests/login/translations.spec.ts index 76317ceb5b95..35c5fc9e9781 100644 --- a/e2e/dotcms-e2e-node/frontend/tests/login/translations.spec.ts +++ b/e2e/dotcms-e2e-node/frontend/tests/login/translations.spec.ts @@ -34,4 +34,4 @@ languages.forEach((list) => { // Assertion of the translation assert(await expect(page.getByTestId("header")).toContainText(translation)); }); -}); +}); \ No newline at end of file diff --git a/e2e/dotcms-e2e-node/frontend/utils/accessibilityUtils.ts b/e2e/dotcms-e2e-node/frontend/utils/accessibilityUtils.ts index dfbf54c162da..f0d0332731ea 100644 --- a/e2e/dotcms-e2e-node/frontend/utils/accessibilityUtils.ts +++ b/e2e/dotcms-e2e-node/frontend/utils/accessibilityUtils.ts @@ -1,30 +1,30 @@ -import * as fs from "node:fs"; -import { Page } from "@playwright/test"; -import AxeBuilder from "@axe-core/playwright"; -import { createHtmlReport } from "axe-html-reporter"; +import * as fs from 'node:fs'; +import { Page } from '@playwright/test'; +import AxeBuilder from '@axe-core/playwright'; +import { createHtmlReport } from 'axe-html-reporter'; export class accessibilityUtils { - page: Page; + page: Page; - constructor(page: Page) { - this.page = page; - } + constructor(page: Page) { + this.page = page; + } - async generateReport(page: Page, description: string) { - const accessibilityScanResults = await new AxeBuilder({ page }).analyze(); - const reportHTML = createHtmlReport({ - results: accessibilityScanResults, - options: { - projectKey: description, - }, - }); + async generateReport(page: Page, description: string) { + const accessibilityScanResults = await new AxeBuilder({page}).analyze(); + const reportHTML = createHtmlReport({ + results: accessibilityScanResults, + options: { + projectKey: description, + }, + }); - if (!fs.existsSync("build/reports")) { - fs.mkdirSync("build/reports", { - recursive: true, - }); + if (!fs.existsSync('build/reports')) { + fs.mkdirSync('build/reports', { + recursive: true, + }); + } + fs.writeFileSync('build/reports/accessibility-report.html', reportHTML); + return accessibilityScanResults; } - fs.writeFileSync("build/reports/accessibility-report.html", reportHTML); - return accessibilityScanResults; - } -} +} \ 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 c77d18f9b6aa..52579fef5c97 100644 --- a/e2e/dotcms-e2e-node/frontend/utils/contentUtils.ts +++ b/e2e/dotcms-e2e-node/frontend/utils/contentUtils.ts @@ -1,513 +1,406 @@ -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"; +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 params - */ - async fillRichTextForm(params: RichTextFormParams) { - const { page, title, body, action, newBody, newTitle } = params; - const dotIframe = page.frameLocator(iFramesLocators.dot_iframe); - - await waitForVisibleAndCallback(page.getByRole("heading"), () => - expect - .soft(page.getByRole("heading")) - .toContainText(contentGeneric.label), - ); - - if (newTitle) { - await dotIframe.locator("#title").clear(); - await dotIframe.locator("#title").fill(newTitle); + page: Page; + + constructor(page: Page) { + this.page = page; } - if (newBody) { - await dotIframe.locator("#block-editor-body div").nth(1).clear(); - await dotIframe.locator("#block-editor-body div").nth(1).fill(newBody); + + /** + * Fill the rich text form + * @param params + */ + async fillRichTextForm(params: RichTextFormParams) { + const {page, title, body, action, newBody, newTitle} = params; + const dotIframe = page.frameLocator(iFramesLocators.dot_iframe); + + await waitForVisibleAndCallback(page.getByRole('heading'), () => + expect.soft(page.getByRole('heading')).toContainText(contentGeneric.label) + ); + + if (newTitle) { + await dotIframe.locator('#title').clear(); + await dotIframe.locator('#title').fill(newTitle); + } + if (newBody) { + await dotIframe.locator('#block-editor-body div').nth(1).clear() + await dotIframe.locator('#block-editor-body div').nth(1).fill(newBody); + } + if (!newTitle && !newBody) { + await dotIframe.locator('#title').fill(title); + await dotIframe.locator('#block-editor-body div').nth(1).fill(body); + } + if (action) { + await dotIframe.getByText(action).first().click(); + } } - if (!newTitle && !newBody) { - await dotIframe.locator("#title").fill(title); - await dotIframe.locator("#block-editor-body div").nth(1).fill(body); + + /** + * 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 () => { + }); + const editor = editFrame.getByLabel('Editor content;Press Alt+F1'); + await editor.click(); // Focus on the editor + await page.keyboard.press('Control+A'); // Select all text (Cmd+A for Mac) + await page.keyboard.press('Backspace'); + 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 () => { + await 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(); + } } - 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()); } - } - - /** - * 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 () => {}, - ); - const editor = editFrame.getByLabel("Editor content;Press Alt+F1"); - await editor.click(); // Focus on the editor - await page.keyboard.press("Control+A"); // Select all text (Cmd+A for Mac) - await page.keyboard.press("Backspace"); - 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 () => { - await expect - .soft(page.getByRole("heading")) - .toContainText(fileAsset.label); + + /** + * 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 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(); - } + 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()); + + await waitForVisibleAndCallback(iframe.getByLabel('structure_inode_popup'), async () => {}); + + const typeLocatorByText = iframe.getByText(typeLocator); + await waitForVisibleAndCallback(typeLocatorByText, () => typeLocatorByText.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 () => {}, - ); - } + /** + * Show query on the content portlet + * @param iframe + */ + async showQuery(iframe: FrameLocator) { + const createOptionsBtnLocator = iframe.getByRole('button', {name: 'createOptions'}); + await waitForVisibleAndCallback(createOptionsBtnLocator, () => createOptionsBtnLocator.click()); - await waitForVisibleAndCallback(dotIframe.locator("#title"), async () => { - await dotIframe.locator("#title").fill(title); - }); + //Validate the search button has a sub-menu + await expect(iframe.getByLabel('Search ▼').getByText('Search')).toBeVisible(); + await expect(iframe.getByText('Show Query')).toBeVisible(); - 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"), - 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(), - ); - - await waitForVisibleAndCallback( - iframe.getByLabel("structure_inode_popup"), - async () => {}, - ); - - const typeLocatorByText = iframe.getByText(typeLocator); - await waitForVisibleAndCallback(typeLocatorByText, () => - typeLocatorByText.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)"), - async () => {}, - ); - 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; - } + // Click on show query + await iframe.getByText('Show Query').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(); - } + /** + * 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); + + 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; } - console.log(`The content with the title ${title} does not exist`); - return null; - } - - /** - * Edit content on the content portlet - * @param params - */ - async editContent(params: RichTextFormParams) { - const { page, title, action } = params; - const contentElement = await this.getContentElement(page, title); - if (!contentElement) { - console.log("Content not found"); - return; + + /** + * 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; } - await contentElement.click(); - await this.fillRichTextForm(params); - if (action) { - await this.workflowExecutionValidationAndClose(page, "Content saved"); + + + /** + * Edit content on the content portlet + * @param params + */ + async editContent(params: RichTextFormParams) { + const {page, title, action} = params; + const contentElement = await this.getContentElement(page, title); + if (!contentElement) { + console.log('Content not found'); + return; + } + await contentElement.click(); + await this.fillRichTextForm(params); + if(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 waitForVisibleAndCallback( - iframe.locator("#contentWrapper"), - async () => {}, - ); - } else if (contentState === "archived") { - await this.performWorkflowAction( - page, - title, - contentProperties.deleteWfAction, - ); - return; - } - await page.waitForLoadState(); + /** + * 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 waitForVisibleAndCallback(iframe.locator('#contentWrapper'), async () => {}); + } else if (contentState === 'archived') { + await this.performWorkflowAction(page, title, contentProperties.deleteWfAction); + return; + } + + await page.waitForLoadState(); + } } - } - - /** - * 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", - }); + + /** + * 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()); } - 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(), - ); - } - - /** - * 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"); - } + + /** + * 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; } - 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(); + + /** + * 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 (template) { - await dotIframe.locator("#widget_templateSel div").first().click(); - await dotIframe.getByText(template).click(); + + + /** + * Validate the download of a file + * @param page + * @param downloadTriggerSelector + */ + async validateDownload(page: Page, downloadTriggerSelector: Locator) { + // Start waiting for the download event + const downloadPromise = page.waitForEvent('download'); + + // Trigger the download + await downloadTriggerSelector.click(); + + // Wait for the download to complete + const download = await downloadPromise; + + // Assert the download was successful + const fileName = download.suggestedFilename(); + console.log(`Downloaded file: ${fileName}`); + expect(fileName).toBeTruthy(); } - 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(); - } - - /** - * Validate the download of a file - * @param page - * @param downloadTriggerSelector - */ - async validateDownload(page: Page, downloadTriggerSelector: Locator) { - // Start waiting for the download event - const downloadPromise = page.waitForEvent("download"); - - // Trigger the download - await downloadTriggerSelector.click(); - - // Wait for the download to complete - const download = await downloadPromise; - - // Assert the download was successful - const fileName = download.suggestedFilename(); - console.log(`Downloaded file: ${fileName}`); - expect(fileName).toBeTruthy(); - } } /** * Base form params */ interface BaseFormParams { - page: Page; - title: string; - action?: string; + page: Page; + title: string; + action?: string; } + interface RichTextFormParams extends BaseFormParams { - body?: string; - action?: string; - newTitle?: string; - newBody?: string; + body?: string, + action?: string + newTitle?: string, + newBody?: string, } /** * Parameter to fill the file asset form params */ interface FileAssetFormParams extends BaseFormParams { - host: string; - editContent: boolean; - fileName?: string; - fromURL?: string; - binaryFileName?: string; - binaryFileText?: 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 1a81af52d9cb..2ab35d19b792 100644 --- a/e2e/dotcms-e2e-node/frontend/utils/dotCMSUtils.ts +++ b/e2e/dotcms-e2e-node/frontend/utils/dotCMSUtils.ts @@ -1,47 +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 { - /** - * 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(); - } + /** + * 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(); + } } /** @@ -49,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 @@ -62,13 +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); - await callback(); +export const waitForAndCallback = async (locator: Locator, state: "attached" | "detached" | "visible" | "hidden", callback: () => Promise): Promise => { + await waitFor(locator, state); + await callback(); }; /** @@ -76,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 diff --git a/e2e/dotcms-e2e-node/frontend/yarn.lock b/e2e/dotcms-e2e-node/frontend/yarn.lock index 6f6233ee5705..6ad7d19e3698 100644 --- a/e2e/dotcms-e2e-node/frontend/yarn.lock +++ b/e2e/dotcms-e2e-node/frontend/yarn.lock @@ -52,7 +52,7 @@ minimatch "^3.1.2" strip-json-comments "^3.1.1" -"@eslint/js@^9.17.0", "@eslint/js@9.18.0": +"@eslint/js@9.18.0", "@eslint/js@^9.17.0": version "9.18.0" resolved "https://registry.npmjs.org/@eslint/js/-/js-9.18.0.tgz" integrity sha512-fK6L7rxcq6/z+AaQMtiFTkvbHkBLNlwyRxHpKawP0x3u9+NC6MQTnFW+AdpwC6gfHTW0051cokQgtTN2FqlxQA== @@ -111,7 +111,7 @@ "@nodelib/fs.stat" "2.0.5" run-parallel "^1.1.9" -"@nodelib/fs.stat@^2.0.2", "@nodelib/fs.stat@2.0.5": +"@nodelib/fs.stat@2.0.5", "@nodelib/fs.stat@^2.0.2": version "2.0.5" resolved "https://registry.npmjs.org/@nodelib/fs.stat/-/fs.stat-2.0.5.tgz" integrity sha512-RkhPPp2zrqDAQA/2jNhnztcPAlv64XdhIp7a7454A5ovI7Bukxgt7MX7udwAu3zg1DcpPU0rz3VV1SeaqvY4+A== @@ -148,7 +148,7 @@ dependencies: undici-types "~6.19.2" -"@typescript-eslint/eslint-plugin@^8.19.0", "@typescript-eslint/eslint-plugin@8.20.0": +"@typescript-eslint/eslint-plugin@8.20.0", "@typescript-eslint/eslint-plugin@^8.19.0": version "8.20.0" resolved "https://registry.npmjs.org/@typescript-eslint/eslint-plugin/-/eslint-plugin-8.20.0.tgz" integrity sha512-naduuphVw5StFfqp4Gq4WhIBE2gN1GEmMUExpJYknZJdRnc+2gDzB8Z3+5+/Kv33hPQRDGzQO/0opHE72lZZ6A== @@ -163,7 +163,7 @@ natural-compare "^1.4.0" ts-api-utils "^2.0.0" -"@typescript-eslint/parser@^8.0.0 || ^8.0.0-alpha.0", "@typescript-eslint/parser@8.20.0": +"@typescript-eslint/parser@8.20.0": version "8.20.0" resolved "https://registry.npmjs.org/@typescript-eslint/parser/-/parser-8.20.0.tgz" integrity sha512-gKXG7A5HMyjDIedBi6bUrDcun8GIjnI8qOwVLiY3rx6T/sHP/19XLJOnIq/FgQvWLHja5JN/LSE7eklNBr612g== @@ -234,7 +234,7 @@ acorn-jsx@^5.3.2: resolved "https://registry.npmjs.org/acorn-jsx/-/acorn-jsx-5.3.2.tgz" integrity sha512-rq9s+JNhf0IChjtDXxllJ7g41oZk5SlXtp0LHwyA5cejwn7vKmKp4pPri6YEePv2PU65sAsegbXtIinmDFDXgQ== -"acorn@^6.0.0 || ^7.0.0 || ^8.0.0", acorn@^8.14.0: +acorn@^8.14.0: version "8.14.0" resolved "https://registry.npmjs.org/acorn/-/acorn-8.14.0.tgz" integrity sha512-cl669nCJTZBsL97OF4kUQm5g5hC2uihk0NxY3WENAC0TYdILVkAyHymAntgxGkl7K+t0cXIrH5siy5S4XkFycA== @@ -273,7 +273,7 @@ asynckit@^0.4.0: resolved "https://registry.npmjs.org/asynckit/-/asynckit-0.4.0.tgz" integrity sha512-Oei9OH4tRh0YqU3GxhX79dM/mwVgvbZJaSNaRk+bshkj0S5cfHcgYakreBjrHwatXKbz+IoIdYLxrKim2MjW0Q== -axe-core@>=3, axe-core@~4.10.2: +axe-core@~4.10.2: version "4.10.2" resolved "https://registry.npmjs.org/axe-core/-/axe-core-4.10.2.tgz" integrity sha512-RE3mdQ7P3FRSe7eqCWoeQ/Z9QXrtniSjp1wUjt5nRC3WIpz5rSCve6o3fsZ2aCpJtrZjSZgjwXAoTO5k4tEI0w== @@ -373,7 +373,7 @@ data-urls@^5.0.0: whatwg-mimetype "^4.0.0" whatwg-url "^14.0.0" -debug@^4.3.1, debug@^4.3.2, debug@^4.3.4, debug@4: +debug@4, debug@^4.3.1, debug@^4.3.2, debug@^4.3.4: version "4.3.7" resolved "https://registry.npmjs.org/debug/-/debug-4.3.7.tgz" integrity sha512-Er2nc/H7RrMXZBFCEim6TCmMk02Z8vLC2Rbi1KEBggpo0fS6l0S1nnapwmIi3yW/+GOJap1Krg4w0Hg80oCqgQ== @@ -440,7 +440,7 @@ eslint-visitor-keys@^4.2.0: resolved "https://registry.npmjs.org/eslint-visitor-keys/-/eslint-visitor-keys-4.2.0.tgz" integrity sha512-UyLnSehNt62FFhSwjZlHmeokpRK59rcz29j+F1/aDgbkbRTk7wIc9XzdoasMUbRNKDM0qQt/+BJ4BrpFeABemw== -"eslint@^6.0.0 || ^7.0.0 || >=8.0.0", "eslint@^8.57.0 || ^9.0.0", eslint@^9.17.0, eslint@>=7.0.0, eslint@>=8.40.0: +eslint@^9.17.0: version "9.18.0" resolved "https://registry.npmjs.org/eslint/-/eslint-9.18.0.tgz" integrity sha512-+waTfRWQlSbpt3KWE+CjrPPYnbq9kfZIYUqapc0uBXyjTp8aYXZDsUH16m39Ryq3NjAVP4tjuF7KaukeqoCoaA== @@ -896,7 +896,7 @@ picomatch@^2.3.1: resolved "https://registry.npmjs.org/picomatch/-/picomatch-2.3.1.tgz" integrity sha512-JU3teHTNjmE2VCGFzuY8EXzCDVwEqB2a8fsIvwaStHhAWJEeVd1o1QD80CU6+ZdEXXSLbSsuLwJjkCBWqRQUVA== -"playwright-core@>= 1.0.0", playwright-core@1.48.2: +playwright-core@1.48.2: version "1.48.2" resolved "https://registry.npmjs.org/playwright-core/-/playwright-core-1.48.2.tgz" integrity sha512-sjjw+qrLFlriJo64du+EK0kJgZzoQPsabGF4lBvsid+3CNIZIYLgnMj9V6JY5VhM2Peh20DJWIVpVljLLnlawA== @@ -1062,7 +1062,7 @@ typescript-eslint@^8.19.0: "@typescript-eslint/parser" "8.20.0" "@typescript-eslint/utils" "8.20.0" -typescript@^5.7.2, typescript@>=4.8.4, "typescript@>=4.8.4 <5.8.0": +typescript@^5.7.2: version "5.7.3" resolved "https://registry.npmjs.org/typescript/-/typescript-5.7.3.tgz" integrity sha512-84MVSjMEHP+FQRPy3pX9sTVV/INIex71s9TL2Gm5FG/WG1SqXeKyZ0k7/blY/4FdOzI12CBy1vGc4og/eus0fw== From d506f5af0bbad7a8b485ab2ee4deea75272b8799 Mon Sep 17 00:00:00 2001 From: Nicolas Molina Date: Thu, 16 Jan 2025 12:44:44 -0400 Subject: [PATCH 20/25] chore(e2e): avoid click --- .../pages/listingContentTypes.pages.ts | 37 +------------------ .../newEditContent/fields/textField.spec.ts | 12 +++++- 2 files changed, 13 insertions(+), 36 deletions(-) diff --git a/e2e/dotcms-e2e-node/frontend/pages/listingContentTypes.pages.ts b/e2e/dotcms-e2e-node/frontend/pages/listingContentTypes.pages.ts index 4d41a0a1f5e9..45b63b4cfc9b 100644 --- a/e2e/dotcms-e2e-node/frontend/pages/listingContentTypes.pages.ts +++ b/e2e/dotcms-e2e-node/frontend/pages/listingContentTypes.pages.ts @@ -1,46 +1,13 @@ -import { Page, expect } from "@playwright/test"; -import { MenuEntriesLocators } from "../locators/navigation/menuLocators"; -import { - GroupEntriesLocators, - ToolEntriesLocators, -} from "../locators/navigation/menuLocators"; -import { dotCMSUtils } from "../utils/dotCMSUtils"; +import { Page } from "@playwright/test"; export class ListingContentTypesPage { - private cmsUtils: dotCMSUtils; - private menuLocators: MenuEntriesLocators; - private groupsLocators: GroupEntriesLocators; - private toolsLocators: ToolEntriesLocators; - constructor(private page: Page) { - this.cmsUtils = new dotCMSUtils(); - this.menuLocators = new MenuEntriesLocators(this.page); - this.groupsLocators = new GroupEntriesLocators(this.page); - this.toolsLocators = new ToolEntriesLocators(this.page); - } + constructor(private page: Page) {} async goToUrl() { await this.page.goto("/dotAdmin/#/content-types-angular"); } - async goTo() { - // 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 this.cmsUtils.login(this.page, username, password); - await this.cmsUtils.navigate( - this.menuLocators.EXPAND, - this.groupsLocators.CONTENT_MODEL, - this.toolsLocators.CONTENT_TYPES, - ); - - // Validate the portlet title - const breadcrumbLocator = this.page.locator("p-breadcrumb"); - await expect(breadcrumbLocator).toContainText("Content Types"); - } - async addNewContentType(name: string) { await this.page.getByRole("button", { name: "" }).click(); await this.page.getByLabel("Content").locator("a").click(); diff --git a/e2e/dotcms-e2e-node/frontend/tests/newEditContent/fields/textField.spec.ts b/e2e/dotcms-e2e-node/frontend/tests/newEditContent/fields/textField.spec.ts index 189c55ec6420..d2af6bcb26d8 100644 --- a/e2e/dotcms-e2e-node/frontend/tests/newEditContent/fields/textField.spec.ts +++ b/e2e/dotcms-e2e-node/frontend/tests/newEditContent/fields/textField.spec.ts @@ -4,13 +4,23 @@ import { ListingContentTypesPage } from "../../../pages/listingContentTypes.page import { ContentTypeFormPage } from "../../../pages/contentTypeForm.page"; import { NewEditContentFormPage } from "../../../pages/newEditContentForm.page"; import { updateFeatureFlag } from "../../../utils/api"; +import { dotCMSUtils } from "../../../utils/dotCMSUtils"; const contentTypeName = faker.lorem.word().toLocaleLowerCase(); test.beforeEach("Navigate to content types", async ({ page, request }) => { + const cmsUtils = new dotCMSUtils(); const listingContentTypesPage = new ListingContentTypesPage(page); const contentTypeFormPage = new ContentTypeFormPage(page); - await listingContentTypesPage.goTo(); + + // 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 listingContentTypesPage.goToUrl(); await listingContentTypesPage.addNewContentType(contentTypeName); await contentTypeFormPage.fillNewContentType(); await listingContentTypesPage.goToUrl(); From b1fdf6000eada00013f3736942daec415f44f24f Mon Sep 17 00:00:00 2001 From: Nicolas Molina Date: Thu, 16 Jan 2025 12:45:00 -0400 Subject: [PATCH 21/25] chore(e2e): apply format --- .../frontend/locators/globalLocators.ts | 50 +- .../locators/navigation/menuLocators.ts | 61 +- .../pages/listingContentTypes.pages.ts | 1 - .../tests/contentSearch/contentData.ts | 64 +- .../contentSearch/contentEditing.spec.ts | 824 ++++++++++------- .../contentSearch/portletIntegrity.spec.ts | 22 +- .../frontend/tests/login/credentialsData.ts | 2 +- .../frontend/tests/login/login.spec.ts | 60 +- .../frontend/tests/login/translations.spec.ts | 2 +- .../newEditContent/fields/textField.spec.ts | 2 +- .../frontend/utils/accessibilityUtils.ts | 48 +- .../frontend/utils/contentUtils.ts | 831 ++++++++++-------- .../frontend/utils/dotCMSUtils.ts | 101 ++- 13 files changed, 1178 insertions(+), 890 deletions(-) diff --git a/e2e/dotcms-e2e-node/frontend/locators/globalLocators.ts b/e2e/dotcms-e2e-node/frontend/locators/globalLocators.ts index 5cfcacb9ba83..1aa3fb4c485b 100644 --- a/e2e/dotcms-e2e-node/frontend/locators/globalLocators.ts +++ b/e2e/dotcms-e2e-node/frontend/locators/globalLocators.ts @@ -2,48 +2,48 @@ * 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"]', - dot_edit_iframe: 'dot-edit-contentlet iframe[name="detailFrame"]', -} + 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 cda521ff3009..58351841889f 100644 --- a/e2e/dotcms-e2e-node/frontend/locators/navigation/menuLocators.ts +++ b/e2e/dotcms-e2e-node/frontend/locators/navigation/menuLocators.ts @@ -1,44 +1,45 @@ -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; - - constructor(page: Page) { - this.EXPAND = page.getByRole('button', { name: '' }); - this.COLLAPSE = page.locator('button[ng-reflect-ng-class="[object Object]"]').first(); - - } -} \ No newline at end of file + readonly EXPAND: Locator; + readonly COLLAPSE: Locator; + + constructor(page: Page) { + this.EXPAND = page.getByRole("button", { name: "" }); + this.COLLAPSE = page + .locator('button[ng-reflect-ng-class="[object Object]"]') + .first(); + } +} diff --git a/e2e/dotcms-e2e-node/frontend/pages/listingContentTypes.pages.ts b/e2e/dotcms-e2e-node/frontend/pages/listingContentTypes.pages.ts index 45b63b4cfc9b..b91c77a51020 100644 --- a/e2e/dotcms-e2e-node/frontend/pages/listingContentTypes.pages.ts +++ b/e2e/dotcms-e2e-node/frontend/pages/listingContentTypes.pages.ts @@ -1,7 +1,6 @@ import { Page } from "@playwright/test"; export class ListingContentTypesPage { - constructor(private page: Page) {} async goToUrl() { diff --git a/e2e/dotcms-e2e-node/frontend/tests/contentSearch/contentData.ts b/e2e/dotcms-e2e-node/frontend/tests/contentSearch/contentData.ts index b907eb366b6f..d678a7d84a05 100644 --- a/e2e/dotcms-e2e-node/frontend/tests/contentSearch/contentData.ts +++ b/e2e/dotcms-e2e-node/frontend/tests/contentSearch/contentData.ts @@ -1,54 +1,52 @@ - /** * 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", +}; /** * Content to create a file asset */ 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", - newFileTextEdited:"Validate you are able to edit text on binary fields", - 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", +}; /** * Content to create a page asset */ 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, +}; export const accessibilityReport = { - name: 'Content Search' -} - - + name: "Content Search", +}; 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 3af7adde8dea..43082ff54aad 100644 --- a/e2e/dotcms-e2e-node/frontend/tests/contentSearch/contentEditing.spec.ts +++ b/e2e/dotcms-e2e-node/frontend/tests/contentSearch/contentEditing.spec.ts @@ -1,129 +1,170 @@ -import {expect, test} from '@playwright/test'; -import {dotCMSUtils, waitForVisibleAndCallback} from '../../utils/dotCMSUtils'; +import { expect, test } from "@playwright/test"; import { - GroupEntriesLocators, - MenuEntriesLocators, - ToolEntriesLocators -} from '../../locators/navigation/menuLocators'; -import {ContentUtils} from "../../utils/contentUtils"; -import {iFramesLocators, contentGeneric, fileAsset, pageAsset} from "../../locators/globalLocators"; + dotCMSUtils, + waitForVisibleAndCallback, +} from "../../utils/dotCMSUtils"; import { - genericContent1, - contentProperties, - fileAssetContent, - pageAssetContent, - accessibilityReport + 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, + accessibilityReport, } from "./contentData"; -import {assert} from "console"; +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, - title: genericContent1.title, - body: genericContent1.body, - action: 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("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, + title: genericContent1.title, + body: genericContent1.body, + action: 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 and make sure you can discard the changes */ -test('Edit a generic content and discard changes', async ({page}) => { - const contentUtils = new ContentUtils(page); - const iframe = page.frameLocator(iFramesLocators.main_iframe); - - await contentUtils.selectTypeOnFilter(page, contentGeneric.locator); - await contentUtils.editContent({ - page, - title: genericContent1.title, - newTitle: genericContent1.newTitle, - newBody: "genericContent1", - }); - await waitForVisibleAndCallback(page.getByTestId ('close-button'), () => page.getByTestId('close-button').click()); - await waitForVisibleAndCallback(page.getByRole('button', { name: 'Close' }), () =>page.getByRole('button', { name: 'Close' }).click()); - await waitForVisibleAndCallback(iframe.locator('#results_table tbody tr').first(), async () => {}); - await contentUtils.validateContentExist(page, genericContent1.title).then(assert); +test("Edit a generic content and discard changes", async ({ page }) => { + const contentUtils = new ContentUtils(page); + const iframe = page.frameLocator(iFramesLocators.main_iframe); + + await contentUtils.selectTypeOnFilter(page, contentGeneric.locator); + await contentUtils.editContent({ + page, + title: genericContent1.title, + newTitle: genericContent1.newTitle, + newBody: "genericContent1", + }); + await waitForVisibleAndCallback(page.getByTestId("close-button"), () => + page.getByTestId("close-button").click(), + ); + await waitForVisibleAndCallback( + page.getByRole("button", { name: "Close" }), + () => page.getByRole("button", { name: "Close" }).click(), + ); + 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); - - await contentUtils.selectTypeOnFilter(page, contentGeneric.locator); - await contentUtils.editContent({ - page, - title: genericContent1.title, - newTitle: genericContent1.newTitle, - newBody: genericContent1.newBody, - action: contentProperties.publishWfAction, - }); - await waitForVisibleAndCallback(iframe.locator('#results_table tbody tr').first(), async () => {}); - 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); + + await contentUtils.selectTypeOnFilter(page, contentGeneric.locator); + await contentUtils.editContent({ + page, + title: genericContent1.title, + newTitle: genericContent1.newTitle, + newBody: genericContent1.newBody, + action: 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); - await contentUtils.deleteContent(page, genericContent1.newTitle); - }); +test("Delete a generic of content", async ({ page }) => { + const contentUtils = new ContentUtils(page); + 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, - title: '', - body: genericContent1.body, - action: 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, + title: "", + body: genericContent1.body, + action: 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 @@ -144,198 +185,280 @@ test('Validate required on text fields', async ({page}) => { /** * Test to validate you are able to add file assets importing from url */ -test('Validate adding file assets from URL', async ({page}) => { - const contentUtils = new ContentUtils(page); - - await contentUtils.addNewContentAction(page, fileAsset.locator, fileAsset.label); - await contentUtils.fillFileAssetForm({ - page, - host: fileAssetContent.host, - title: fileAssetContent.title, - editContent: true, - action: contentProperties.publishWfAction, - fromURL: fileAssetContent.fromURL - }); - await contentUtils.workflowExecutionValidationAndClose(page, 'Content saved'); - await expect(contentUtils.validateContentExist(page, 'DotCMS-logo.svg')).resolves.toBeTruthy(); +test("Validate adding file assets from URL", async ({ page }) => { + const contentUtils = new ContentUtils(page); + + await contentUtils.addNewContentAction( + page, + fileAsset.locator, + fileAsset.label, + ); + await contentUtils.fillFileAssetForm({ + page, + host: fileAssetContent.host, + title: fileAssetContent.title, + editContent: true, + action: contentProperties.publishWfAction, + fromURL: fileAssetContent.fromURL, + }); + 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 creating a new file */ -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); - await contentUtils.fillFileAssetForm({ - page, - host: fileAssetContent.host, - editContent: false, - title: fileAssetContent.title, - action: contentProperties.publishWfAction, - binaryFileName: fileAssetContent.newFileName, - binaryFileText: fileAssetContent.newFileText - }); - await contentUtils.workflowExecutionValidationAndClose(page, 'Content saved'); - await contentUtils.validateContentExist(page, fileAssetContent.newFileName).then(assert); +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, + ); + await contentUtils.fillFileAssetForm({ + page, + host: fileAssetContent.host, + editContent: false, + title: fileAssetContent.title, + action: contentProperties.publishWfAction, + binaryFileName: fileAssetContent.newFileName, + binaryFileText: fileAssetContent.newFileText, + }); + await contentUtils.workflowExecutionValidationAndClose(page, "Content saved"); + await contentUtils + .validateContentExist(page, fileAssetContent.newFileName) + .then(assert); }); /** * Test to validate you are able to edit file assets text */ -test('Validate you can 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) - ); - - await contentUtils.fillFileAssetForm({ - page, - host: fileAssetContent.host, - editContent: true, - title: fileAssetContent.title, - action: contentProperties.publishWfAction, - binaryFileName: fileAssetContent.newFileName, - binaryFileText: fileAssetContent.newFileTextEdited - }); - await contentUtils.workflowExecutionValidationAndClose(page, 'Content saved'); - - await contentUtils.selectTypeOnFilter(page, fileAsset.locator); - await (await contentUtils.getContentElement(page, fileAssetContent.newFileName)).click(); - const editIframe = page.frameLocator(iFramesLocators.dot_edit_iframe); - await expect(editIframe.getByRole('code')).toHaveText(fileAssetContent.newFileTextEdited); +test("Validate you can 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), + ); + + await contentUtils.fillFileAssetForm({ + page, + host: fileAssetContent.host, + editContent: true, + title: fileAssetContent.title, + action: contentProperties.publishWfAction, + binaryFileName: fileAssetContent.newFileName, + binaryFileText: fileAssetContent.newFileTextEdited, + }); + await contentUtils.workflowExecutionValidationAndClose(page, "Content saved"); + + await contentUtils.selectTypeOnFilter(page, fileAsset.locator); + await ( + await contentUtils.getContentElement(page, fileAssetContent.newFileName) + ).click(); + const editIframe = page.frameLocator(iFramesLocators.dot_edit_iframe); + await expect(editIframe.getByRole("code")).toHaveText( + fileAssetContent.newFileTextEdited, + ); }); /** * Test to validate you are able to remove file assets from the content */ -test('Validate you are able to delete file on binary fields', async ({page}) => { - const contentUtils = new ContentUtils(page); - const mainFrame = page.frameLocator(iFramesLocators.main_iframe); - - await contentUtils.selectTypeOnFilter(page, fileAsset.locator); - await waitForVisibleAndCallback(mainFrame.locator('#contentWrapper'), async () => {}); - 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 detailFrame = page.frameLocator(iFramesLocators.dot_edit_iframe); - await detailFrame.getByRole('button', { name: ' Remove' }).click(); - await waitForVisibleAndCallback(detailFrame.getByTestId('ui-message-icon-container'), async () => {}); - await detailFrame.getByText('Publish', { exact: true }).click(); - await expect(detailFrame.getByText('The field File Asset is')).toBeVisible(); +test("Validate you are able to delete file on binary fields", async ({ + page, +}) => { + const contentUtils = new ContentUtils(page); + const mainFrame = page.frameLocator(iFramesLocators.main_iframe); + + await contentUtils.selectTypeOnFilter(page, fileAsset.locator); + await waitForVisibleAndCallback( + mainFrame.locator("#contentWrapper"), + async () => {}, + ); + 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 detailFrame = page.frameLocator(iFramesLocators.dot_edit_iframe); + await detailFrame.getByRole("button", { name: " Remove" }).click(); + await waitForVisibleAndCallback( + detailFrame.getByTestId("ui-message-icon-container"), + async () => {}, + ); + await detailFrame.getByText("Publish", { exact: true }).click(); + await expect(detailFrame.getByText("The field File Asset is")).toBeVisible(); }); /** * Test to validate the get info on of the binary field on file assets */ -test('Validate file assets show corresponding information', async ({page}) => { - const contentUtils = new ContentUtils(page); - const mainFrame = page.frameLocator(iFramesLocators.main_iframe); - - await contentUtils.selectTypeOnFilter(page, fileAsset.locator); - await waitForVisibleAndCallback(mainFrame.locator('#contentWrapper'), async () => {}); - await (await contentUtils.getContentElement(page, fileAssetContent.newFileName)).click(); - await waitForVisibleAndCallback(page.getByRole('heading'), () => - expect.soft(page.getByRole('heading')).toContainText(fileAsset.label) - ); - - const detailFrame = page.frameLocator(iFramesLocators.dot_edit_iframe); - await detailFrame.getByTestId('info-btn').click(); - await waitForVisibleAndCallback(detailFrame.getByText('Bytes'), async () => {}); - await expect(detailFrame.getByText('Bytes')).toBeVisible(); - await expect(detailFrame.getByTestId('resource-link-FileLink')).toContainText("http"); - await expect(detailFrame.getByTestId('resource-link-Resource-Link')).not.toBeEmpty(); - await expect(detailFrame.getByTestId('resource-link-VersionPath')).not.toBeEmpty(); - await expect(detailFrame.getByTestId('resource-link-IdPath')).not.toBeEmpty(); +test("Validate file assets show corresponding information", async ({ + page, +}) => { + const contentUtils = new ContentUtils(page); + const mainFrame = page.frameLocator(iFramesLocators.main_iframe); + + await contentUtils.selectTypeOnFilter(page, fileAsset.locator); + await waitForVisibleAndCallback( + mainFrame.locator("#contentWrapper"), + async () => {}, + ); + await ( + await contentUtils.getContentElement(page, fileAssetContent.newFileName) + ).click(); + await waitForVisibleAndCallback(page.getByRole("heading"), () => + expect.soft(page.getByRole("heading")).toContainText(fileAsset.label), + ); + + const detailFrame = page.frameLocator(iFramesLocators.dot_edit_iframe); + await detailFrame.getByTestId("info-btn").click(); + await waitForVisibleAndCallback( + detailFrame.getByText("Bytes"), + async () => {}, + ); + await expect(detailFrame.getByText("Bytes")).toBeVisible(); + await expect(detailFrame.getByTestId("resource-link-FileLink")).toContainText( + "http", + ); + await expect( + detailFrame.getByTestId("resource-link-Resource-Link"), + ).not.toBeEmpty(); + await expect( + detailFrame.getByTestId("resource-link-VersionPath"), + ).not.toBeEmpty(); + await expect(detailFrame.getByTestId("resource-link-IdPath")).not.toBeEmpty(); }); //* Test to validate the download of binary fields on file assets -test('Validate the download of binary fields on file assets', async ({page}) => { - const contentUtils = new ContentUtils(page); - const mainFrame = page.frameLocator(iFramesLocators.main_iframe); - - await contentUtils.selectTypeOnFilter(page, fileAsset.locator); - await waitForVisibleAndCallback(mainFrame.locator('#contentWrapper'), async () => {}); - await (await contentUtils.getContentElement(page, fileAssetContent.newFileName)).click(); - await waitForVisibleAndCallback(page.getByRole('heading'), () => - expect.soft(page.getByRole('heading')).toContainText(fileAsset.label) - ); - const detailFrame = page.frameLocator(iFramesLocators.dot_edit_iframe); - const downloadLink = detailFrame.getByTestId('download-btn'); - await contentUtils.validateDownload(page, downloadLink); +test("Validate the download of binary fields on file assets", async ({ + page, +}) => { + const contentUtils = new ContentUtils(page); + const mainFrame = page.frameLocator(iFramesLocators.main_iframe); + + await contentUtils.selectTypeOnFilter(page, fileAsset.locator); + await waitForVisibleAndCallback( + mainFrame.locator("#contentWrapper"), + async () => {}, + ); + await ( + await contentUtils.getContentElement(page, fileAssetContent.newFileName) + ).click(); + await waitForVisibleAndCallback(page.getByRole("heading"), () => + expect.soft(page.getByRole("heading")).toContainText(fileAsset.label), + ); + const detailFrame = page.frameLocator(iFramesLocators.dot_edit_iframe); + const downloadLink = detailFrame.getByTestId("download-btn"); + await contentUtils.validateDownload(page, downloadLink); }); /** * Test to validate the required on file asset fields */ -test('Validate the required on file asset fields', async ({page}) => { - const contentUtils = new ContentUtils(page); - const detailsFrame = page.frameLocator(iFramesLocators.dot_iframe); - - await contentUtils.addNewContentAction(page, fileAsset.locator, fileAsset.label); - await contentUtils.fillFileAssetForm({ - page, - host: fileAssetContent.host, - editContent: false, - title: fileAssetContent.title, - action: contentProperties.publishWfAction - }); - await waitForVisibleAndCallback(detailsFrame.getByText('Error x'), async () => {}); - 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 contentUtils = new ContentUtils(page); + const detailsFrame = page.frameLocator(iFramesLocators.dot_iframe); + + await contentUtils.addNewContentAction( + page, + fileAsset.locator, + fileAsset.label, + ); + await contentUtils.fillFileAssetForm({ + page, + host: fileAssetContent.host, + editContent: false, + title: fileAssetContent.title, + action: contentProperties.publishWfAction, + }); + await waitForVisibleAndCallback( + detailsFrame.getByText("Error x"), + async () => {}, + ); + const 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 contentUtils = new ContentUtils(page); - const detailsFrame = page.frameLocator(iFramesLocators.dot_iframe); - - await contentUtils.addNewContentAction(page, fileAsset.locator, fileAsset.label); - await detailsFrame.locator('#fileName').fill('test'); - await contentUtils.fillFileAssetForm({ - page, - host: fileAssetContent.host, - editContent: false, - title: fileAssetContent.title, - binaryFileName: fileAssetContent.newFileName, - binaryFileText: fileAssetContent.newFileText - }); - 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 contentUtils = new ContentUtils(page); + const detailsFrame = page.frameLocator(iFramesLocators.dot_iframe); + + await contentUtils.addNewContentAction( + page, + fileAsset.locator, + fileAsset.label, + ); + await detailsFrame.locator("#fileName").fill("test"); + await contentUtils.fillFileAssetForm({ + page, + host: fileAssetContent.host, + editContent: false, + title: fileAssetContent.title, + binaryFileName: fileAssetContent.newFileName, + binaryFileText: fileAssetContent.newFileText, + }); + 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 contentUtils = new ContentUtils(page); - const detailsFrame = page.frameLocator(iFramesLocators.dot_iframe); - - await contentUtils.addNewContentAction(page, fileAsset.locator, fileAsset.label); - await detailsFrame.locator('#fileName').fill('test'); - await contentUtils.fillFileAssetForm({ - page, - host: fileAssetContent.host, - editContent: false, - title: fileAssetContent.title, - binaryFileName: fileAssetContent.newFileName, - binaryFileText: fileAssetContent.newFileText - }); - const replaceText = detailsFrame.getByText('Do you want to replace the'); - await waitForVisibleAndCallback(replaceText, () => 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 contentUtils = new ContentUtils(page); + const detailsFrame = page.frameLocator(iFramesLocators.dot_iframe); + + await contentUtils.addNewContentAction( + page, + fileAsset.locator, + fileAsset.label, + ); + await detailsFrame.locator("#fileName").fill("test"); + await contentUtils.fillFileAssetForm({ + page, + host: fileAssetContent.host, + editContent: false, + title: fileAssetContent.title, + binaryFileName: fileAssetContent.newFileName, + binaryFileText: fileAssetContent.newFileText, + }); + const replaceText = detailsFrame.getByText("Do you want to replace the"); + await waitForVisibleAndCallback(replaceText, () => + expect(replaceText).toBeVisible(), + ); + await detailsFrame.getByLabel("No").click(); + await expect(detailsFrame.locator("#fileName")).toHaveValue("test"); }); /** @@ -357,117 +480,158 @@ test('Validate the auto complete on FileName field rejecting change', async ({pa /** * Test to validate you are able to delete a file asset content */ -test('Delete a file asset content', async ({ page }) => { - await new ContentUtils(page).deleteContent(page, fileAssetContent.title); +test("Delete a file asset content", async ({ page }) => { + await new ContentUtils(page).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); - await contentUtils.fillPageAssetForm({ - 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, - }); - const dataFrame = page.frameLocator(iFramesLocators.dataTestId); - await waitForVisibleAndCallback(dataFrame.getByRole('banner'), async () => {}); - 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, + ); + await contentUtils.fillPageAssetForm({ + 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, + }); + 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 URL is unique on pages */ -test('Validate URL is unique on pages', async ({page}) => { - const contentUtils = new ContentUtils(page); - - await contentUtils.addNewContentAction(page, pageAsset.locator, pageAsset.label); - await contentUtils.fillPageAssetForm({ - 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 page.frameLocator('dot-iframe-dialog iframe[name="detailFrame"]').getByText('Another Page with the same').click(); - - const iframe = page.frameLocator(iFramesLocators.dot_iframe); - await expect(iframe.getByText('Another Page with the same')).toBeVisible(); +test("Validate URL is unique on pages", async ({ page }) => { + const contentUtils = new ContentUtils(page); + + await contentUtils.addNewContentAction( + page, + pageAsset.locator, + pageAsset.label, + ); + await contentUtils.fillPageAssetForm({ + 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 page + .frameLocator('dot-iframe-dialog iframe[name="detailFrame"]') + .getByText("Another Page with the same") + .click(); + + const iframe = page.frameLocator(iFramesLocators.dot_iframe); + await expect(iframe.getByText("Another Page with the same")).toBeVisible(); }); /** * 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); - await contentUtils.fillPageAssetForm({ - page, - title: "", - host: pageAssetContent.host, - template: pageAssetContent.template, - showOnMenu: pageAssetContent.showOnMenu, - action: contentProperties.publishWfAction - }); - 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("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, + ); + await contentUtils.fillPageAssetForm({ + page, + title: "", + host: pageAssetContent.host, + template: pageAssetContent.template, + showOnMenu: pageAssetContent.showOnMenu, + action: contentProperties.publishWfAction, + }); + 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); - await contentUtils.fillPageAssetForm({ - page, - title: pageAssetContent.title, - host: pageAssetContent.host, - template: pageAssetContent.template, - showOnMenu: pageAssetContent.showOnMenu - }); - - 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, + ); + await contentUtils.fillPageAssetForm({ + page, + title: pageAssetContent.title, + host: pageAssetContent.host, + template: pageAssetContent.template, + showOnMenu: pageAssetContent.showOnMenu, + }); + + await expect(detailFrame.locator("#url")).toHaveValue( + pageAssetContent.title.toLowerCase(), + ); + await expect(detailFrame.locator("#friendlyName")).toHaveValue( + pageAssetContent.title, + ); }); /** * Test to validate you are able to unpublish a page asset */ -test('Validate you are able to unpublish pages', async ({page}) => { - const contentUtils = new ContentUtils(page); - await contentUtils.selectTypeOnFilter(page, pageAsset.locator); - await contentUtils.performWorkflowAction(page, pageAssetContent.title, contentProperties.unpublishWfAction); - await contentUtils.getContentState(page, pageAssetContent.title).then(assert); +test("Validate you are able to unpublish pages", async ({ page }) => { + const contentUtils = new ContentUtils(page); + await contentUtils.selectTypeOnFilter(page, pageAsset.locator); + await contentUtils.performWorkflowAction( + page, + pageAssetContent.title, + contentProperties.unpublishWfAction, + ); + await contentUtils.getContentState(page, pageAssetContent.title).then(assert); }); /** * Test to validate you are able to delete pages */ -test('Validate you are able to delete pages', async ({page}) => { - const contentUtils = new ContentUtils(page); - await contentUtils.selectTypeOnFilter(page, pageAsset.locator); - await contentUtils.deleteContent(page, pageAssetContent.title); +test("Validate you are able to delete pages", async ({ page }) => { + const contentUtils = new ContentUtils(page); + await contentUtils.selectTypeOnFilter(page, pageAsset.locator); + await contentUtils.deleteContent(page, pageAssetContent.title); }); /** @@ -479,4 +643,4 @@ test('accessibility test', async ({page}) => { const accessibilityScanResults = await accessibility.generateReport(page, accessibilityReport.name); expect(accessibilityScanResults.violations).toEqual([]); // 5 }); - */ \ 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 4ff6e62d0db2..40eafaa6c148 100644 --- a/e2e/dotcms-e2e-node/frontend/tests/contentSearch/portletIntegrity.spec.ts +++ b/e2e/dotcms-e2e-node/frontend/tests/contentSearch/portletIntegrity.spec.ts @@ -72,7 +72,7 @@ test("Search filter", async ({ page }) => { await contentUtils.fillRichTextForm({ page, title: genericContent1.title, - body: genericContent1.body, + body: genericContent1.body, action: contentProperties.publishWfAction, }); await contentUtils.workflowExecutionValidationAndClose(page, "Content saved"); @@ -224,14 +224,14 @@ test("Validate the clear button in the search filter", async ({ page }) => { // Validate the search filter has been cleared await expect( - iframe - .locator('input[name="scheme_id_select"]') - , - ).toHaveAttribute("value", "catchall"); - await expect( - iframe.locator('input[name="step_id_select"]'), + iframe.locator('input[name="scheme_id_select"]'), ).toHaveAttribute("value", "catchall"); - await expect(iframe.locator("#showingSelect")).toHaveAttribute("value", + await expect(iframe.locator('input[name="step_id_select"]')).toHaveAttribute( + "value", + "catchall", + ); + await expect(iframe.locator("#showingSelect")).toHaveAttribute( + "value", "All", ); }); @@ -250,9 +250,7 @@ test("Validate the hide button collapse the filter", async ({ page }) => { ); await page.waitForTimeout(1000); - await expect( - iframe.getByRole("link", { name: "Advanced" }), - ).toBeHidden(); + await expect(iframe.getByRole("link", { name: "Advanced" })).toBeHidden(); // Click on the hide button await iframe.getByRole("link", { name: "Hide" }).click(); @@ -260,4 +258,4 @@ test("Validate the hide button collapse the filter", async ({ page }) => { // Validate the filter has been collapsed await expect(iframe.getByRole("link", { name: "Advanced" })).toBeVisible(); -}); \ No newline at end of file +}); diff --git a/e2e/dotcms-e2e-node/frontend/tests/login/credentialsData.ts b/e2e/dotcms-e2e-node/frontend/tests/login/credentialsData.ts index d4ee12051553..444ffe912a36 100644 --- a/e2e/dotcms-e2e-node/frontend/tests/login/credentialsData.ts +++ b/e2e/dotcms-e2e-node/frontend/tests/login/credentialsData.ts @@ -28,4 +28,4 @@ export const wrong1 = { export const wrong2 = { username: "chris2@dotcms.com", password: "chris", -}; \ No newline at end of file +}; 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 3b7a07021d81..c42804890012 100644 --- a/e2e/dotcms-e2e-node/frontend/tests/login/login.spec.ts +++ b/e2e/dotcms-e2e-node/frontend/tests/login/login.spec.ts @@ -1,47 +1,49 @@ -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}); - }); -}); \ No newline at end of file + // Assertion and further test steps + await expect(page.getByTestId("message")).toBeVisible({ timeout: 30000 }); + }); +}); diff --git a/e2e/dotcms-e2e-node/frontend/tests/login/translations.spec.ts b/e2e/dotcms-e2e-node/frontend/tests/login/translations.spec.ts index 35c5fc9e9781..76317ceb5b95 100644 --- a/e2e/dotcms-e2e-node/frontend/tests/login/translations.spec.ts +++ b/e2e/dotcms-e2e-node/frontend/tests/login/translations.spec.ts @@ -34,4 +34,4 @@ languages.forEach((list) => { // Assertion of the translation assert(await expect(page.getByTestId("header")).toContainText(translation)); }); -}); \ No newline at end of file +}); diff --git a/e2e/dotcms-e2e-node/frontend/tests/newEditContent/fields/textField.spec.ts b/e2e/dotcms-e2e-node/frontend/tests/newEditContent/fields/textField.spec.ts index d2af6bcb26d8..ecd7f5cdae38 100644 --- a/e2e/dotcms-e2e-node/frontend/tests/newEditContent/fields/textField.spec.ts +++ b/e2e/dotcms-e2e-node/frontend/tests/newEditContent/fields/textField.spec.ts @@ -19,7 +19,7 @@ test.beforeEach("Navigate to content types", async ({ page, request }) => { // Login to dotCMS await cmsUtils.login(page, username, password); - + await listingContentTypesPage.goToUrl(); await listingContentTypesPage.addNewContentType(contentTypeName); await contentTypeFormPage.fillNewContentType(); diff --git a/e2e/dotcms-e2e-node/frontend/utils/accessibilityUtils.ts b/e2e/dotcms-e2e-node/frontend/utils/accessibilityUtils.ts index f0d0332731ea..dfbf54c162da 100644 --- a/e2e/dotcms-e2e-node/frontend/utils/accessibilityUtils.ts +++ b/e2e/dotcms-e2e-node/frontend/utils/accessibilityUtils.ts @@ -1,30 +1,30 @@ -import * as fs from 'node:fs'; -import { Page } from '@playwright/test'; -import AxeBuilder from '@axe-core/playwright'; -import { createHtmlReport } from 'axe-html-reporter'; +import * as fs from "node:fs"; +import { Page } from "@playwright/test"; +import AxeBuilder from "@axe-core/playwright"; +import { createHtmlReport } from "axe-html-reporter"; export class accessibilityUtils { - page: Page; + page: Page; - constructor(page: Page) { - this.page = page; - } + constructor(page: Page) { + this.page = page; + } - async generateReport(page: Page, description: string) { - const accessibilityScanResults = await new AxeBuilder({page}).analyze(); - const reportHTML = createHtmlReport({ - results: accessibilityScanResults, - options: { - projectKey: description, - }, - }); + async generateReport(page: Page, description: string) { + const accessibilityScanResults = await new AxeBuilder({ page }).analyze(); + const reportHTML = createHtmlReport({ + results: accessibilityScanResults, + options: { + projectKey: description, + }, + }); - if (!fs.existsSync('build/reports')) { - fs.mkdirSync('build/reports', { - recursive: true, - }); - } - fs.writeFileSync('build/reports/accessibility-report.html', reportHTML); - return accessibilityScanResults; + if (!fs.existsSync("build/reports")) { + fs.mkdirSync("build/reports", { + recursive: true, + }); } -} \ No newline at end of file + fs.writeFileSync("build/reports/accessibility-report.html", reportHTML); + return accessibilityScanResults; + } +} diff --git a/e2e/dotcms-e2e-node/frontend/utils/contentUtils.ts b/e2e/dotcms-e2e-node/frontend/utils/contentUtils.ts index 52579fef5c97..c77d18f9b6aa 100644 --- a/e2e/dotcms-e2e-node/frontend/utils/contentUtils.ts +++ b/e2e/dotcms-e2e-node/frontend/utils/contentUtils.ts @@ -1,406 +1,513 @@ -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"; - +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; + page: Page; + + constructor(page: Page) { + this.page = page; + } + + /** + * Fill the rich text form + * @param params + */ + async fillRichTextForm(params: RichTextFormParams) { + const { page, title, body, action, newBody, newTitle } = params; + const dotIframe = page.frameLocator(iFramesLocators.dot_iframe); + + await waitForVisibleAndCallback(page.getByRole("heading"), () => + expect + .soft(page.getByRole("heading")) + .toContainText(contentGeneric.label), + ); + + if (newTitle) { + await dotIframe.locator("#title").clear(); + await dotIframe.locator("#title").fill(newTitle); } - - /** - * Fill the rich text form - * @param params - */ - async fillRichTextForm(params: RichTextFormParams) { - const {page, title, body, action, newBody, newTitle} = params; - const dotIframe = page.frameLocator(iFramesLocators.dot_iframe); - - await waitForVisibleAndCallback(page.getByRole('heading'), () => - expect.soft(page.getByRole('heading')).toContainText(contentGeneric.label) - ); - - if (newTitle) { - await dotIframe.locator('#title').clear(); - await dotIframe.locator('#title').fill(newTitle); - } - if (newBody) { - await dotIframe.locator('#block-editor-body div').nth(1).clear() - await dotIframe.locator('#block-editor-body div').nth(1).fill(newBody); - } - if (!newTitle && !newBody) { - await dotIframe.locator('#title').fill(title); - await dotIframe.locator('#block-editor-body div').nth(1).fill(body); - } - if (action) { - await dotIframe.getByText(action).first().click(); - } + if (newBody) { + await dotIframe.locator("#block-editor-body div").nth(1).clear(); + await dotIframe.locator("#block-editor-body div").nth(1).fill(newBody); } - - /** - * 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 () => { - }); - const editor = editFrame.getByLabel('Editor content;Press Alt+F1'); - await editor.click(); // Focus on the editor - await page.keyboard.press('Control+A'); // Select all text (Cmd+A for Mac) - await page.keyboard.press('Backspace'); - 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 () => { - await 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(); - } + if (!newTitle && !newBody) { + await dotIframe.locator("#title").fill(title); + await dotIframe.locator("#block-editor-body div").nth(1).fill(body); } - - /** - * 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()); + if (action) { + await dotIframe.getByText(action).first().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'), async () => { + } + + /** + * 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 () => {}, + ); + const editor = editFrame.getByLabel("Editor content;Press Alt+F1"); + await editor.click(); // Focus on the editor + await page.keyboard.press("Control+A"); // Select all text (Cmd+A for Mac) + await page.keyboard.press("Backspace"); + 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 () => { + await expect + .soft(page.getByRole("heading")) + .toContainText(fileAsset.label); }); - 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()); - - await waitForVisibleAndCallback(iframe.getByLabel('structure_inode_popup'), async () => {}); - - const typeLocatorByText = iframe.getByText(typeLocator); - await waitForVisibleAndCallback(typeLocatorByText, () => typeLocatorByText.click()); + 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(); + } } - /** - * 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(); + 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 () => {}, + ); } - /** - * 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); - - 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; - } - } + await waitForVisibleAndCallback(dotIframe.locator("#title"), async () => { + await dotIframe.locator("#title").fill(title); + }); - console.log(`The text "${text}" does not exist in the results table.`); - return false; + if (action) { + await dotIframe.getByText(action).first().click(); } - - /** - * 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; + } + + /** + * 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"), + 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(), + ); + + await waitForVisibleAndCallback( + iframe.getByLabel("structure_inode_popup"), + async () => {}, + ); + + const typeLocatorByText = iframe.getByText(typeLocator); + await waitForVisibleAndCallback(typeLocatorByText, () => + typeLocatorByText.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)"), + async () => {}, + ); + 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; + } } - - /** - * Edit content on the content portlet - * @param params - */ - async editContent(params: RichTextFormParams) { - const {page, title, action} = params; - const contentElement = await this.getContentElement(page, title); - if (!contentElement) { - console.log('Content not found'); - return; - } - await contentElement.click(); - await this.fillRichTextForm(params); - if(action) { - await this.workflowExecutionValidationAndClose(page, 'Content saved'); - } + 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(); + } } - - /** - * 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 waitForVisibleAndCallback(iframe.locator('#contentWrapper'), async () => {}); - } else if (contentState === 'archived') { - await this.performWorkflowAction(page, title, contentProperties.deleteWfAction); - return; - } - - await page.waitForLoadState(); - } + console.log(`The content with the title ${title} does not exist`); + return null; + } + + /** + * Edit content on the content portlet + * @param params + */ + async editContent(params: RichTextFormParams) { + const { page, title, action } = params; + const contentElement = await this.getContentElement(page, title); + if (!contentElement) { + console.log("Content not found"); + return; } - - /** - * 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()); + await contentElement.click(); + await this.fillRichTextForm(params); + if (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 waitForVisibleAndCallback( + iframe.locator("#contentWrapper"), + async () => {}, + ); + } else if (contentState === "archived") { + await this.performWorkflowAction( + page, + title, + contentProperties.deleteWfAction, + ); + return; + } - /** - * 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; + await page.waitForLoadState(); } - - - /** - * 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(); + } + + /** + * 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(), + ); + } + + /** + * 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"); + } } - - /** - * Validate the download of a file - * @param page - * @param downloadTriggerSelector - */ - async validateDownload(page: Page, downloadTriggerSelector: Locator) { - // Start waiting for the download event - const downloadPromise = page.waitForEvent('download'); - - // Trigger the download - await downloadTriggerSelector.click(); - - // Wait for the download to complete - const download = await downloadPromise; - - // Assert the download was successful - const fileName = download.suggestedFilename(); - console.log(`Downloaded file: ${fileName}`); - expect(fileName).toBeTruthy(); + 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(); + } + + /** + * Validate the download of a file + * @param page + * @param downloadTriggerSelector + */ + async validateDownload(page: Page, downloadTriggerSelector: Locator) { + // Start waiting for the download event + const downloadPromise = page.waitForEvent("download"); + + // Trigger the download + await downloadTriggerSelector.click(); + + // Wait for the download to complete + const download = await downloadPromise; + + // Assert the download was successful + const fileName = download.suggestedFilename(); + console.log(`Downloaded file: ${fileName}`); + expect(fileName).toBeTruthy(); + } } /** * Base form params */ interface BaseFormParams { - page: Page; - title: string; - action?: string; + page: Page; + title: string; + action?: string; } - interface RichTextFormParams extends BaseFormParams { - body?: string, - action?: string - newTitle?: string, - newBody?: string, + body?: string; + action?: string; + newTitle?: string; + newBody?: string; } /** * Parameter to fill the file asset form params */ interface FileAssetFormParams extends BaseFormParams { - host: string; - editContent : boolean; - fileName?: string; - fromURL?: string; - binaryFileName?: string; - binaryFileText?: 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 2ab35d19b792..1a81af52d9cb 100644 --- a/e2e/dotcms-e2e-node/frontend/utils/dotCMSUtils.ts +++ b/e2e/dotcms-e2e-node/frontend/utils/dotCMSUtils.ts @@ -1,38 +1,47 @@ -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 { + /** + * 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(); + } } /** @@ -40,9 +49,12 @@ 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 @@ -50,9 +62,13 @@ export const waitFor = async (locator: Locator, state: "attached" | "detached" | * @param state * @param callback */ -export const waitForAndCallback = async (locator: Locator, state: "attached" | "detached" | "visible" | "hidden", callback: () => Promise): Promise => { - await waitFor(locator, state); - await callback(); +export const waitForAndCallback = async ( + locator: Locator, + state: "attached" | "detached" | "visible" | "hidden", + callback: () => Promise, +): Promise => { + await waitFor(locator, state); + await callback(); }; /** @@ -60,6 +76,9 @@ export const waitForAndCallback = async (locator: Locator, state: "attached" | " * @param locator * @param callback */ -export const waitForVisibleAndCallback = async (locator: Locator, callback: () => Promise): Promise => { - await waitForAndCallback(locator, 'visible', callback); -}; \ No newline at end of file +export const waitForVisibleAndCallback = async ( + locator: Locator, + callback: () => Promise, +): Promise => { + await waitForAndCallback(locator, "visible", callback); +}; From 43ea6cc42af05961964b585a0b5a563f96cd17cb Mon Sep 17 00:00:00 2001 From: Nicolas Molina Date: Thu, 16 Jan 2025 14:34:03 -0400 Subject: [PATCH 22/25] chore(e2e): fix error with feature flags --- .../pages/listingContentTypes.pages.ts | 21 ++++++++++++-- .../newEditContent/fields/textField.spec.ts | 29 ++++--------------- 2 files changed, 25 insertions(+), 25 deletions(-) diff --git a/e2e/dotcms-e2e-node/frontend/pages/listingContentTypes.pages.ts b/e2e/dotcms-e2e-node/frontend/pages/listingContentTypes.pages.ts index b91c77a51020..461b8fe2b807 100644 --- a/e2e/dotcms-e2e-node/frontend/pages/listingContentTypes.pages.ts +++ b/e2e/dotcms-e2e-node/frontend/pages/listingContentTypes.pages.ts @@ -1,12 +1,29 @@ -import { Page } from "@playwright/test"; +import { APIRequestContext, Page } from "@playwright/test"; +import { updateFeatureFlag } from "../utils/api"; export class ListingContentTypesPage { - constructor(private page: Page) {} + constructor(private page: Page, private request: APIRequestContext) {} async goToUrl() { await this.page.goto("/dotAdmin/#/content-types-angular"); } + async toggleNewContentEditor(boolean: boolean) { + await updateFeatureFlag(this.request, { + key: "DOT_FEATURE_FLAG_NEW_EDIT_PAGE", + value: boolean, + }); + await updateFeatureFlag(this.request, { + key: "DOT_CONTENT_EDITOR2_ENABLED", + value: boolean, + }); + await updateFeatureFlag(this.request, { + key: "DOT_CONTENT_EDITOR2_CONTENT_TYPE", + value: "*", + }); + await this.page.reload(); + } + async addNewContentType(name: string) { await this.page.getByRole("button", { name: "" }).click(); await this.page.getByLabel("Content").locator("a").click(); diff --git a/e2e/dotcms-e2e-node/frontend/tests/newEditContent/fields/textField.spec.ts b/e2e/dotcms-e2e-node/frontend/tests/newEditContent/fields/textField.spec.ts index ecd7f5cdae38..d7347955ef17 100644 --- a/e2e/dotcms-e2e-node/frontend/tests/newEditContent/fields/textField.spec.ts +++ b/e2e/dotcms-e2e-node/frontend/tests/newEditContent/fields/textField.spec.ts @@ -3,14 +3,12 @@ import { faker } from "@faker-js/faker"; import { ListingContentTypesPage } from "../../../pages/listingContentTypes.pages"; import { ContentTypeFormPage } from "../../../pages/contentTypeForm.page"; import { NewEditContentFormPage } from "../../../pages/newEditContentForm.page"; -import { updateFeatureFlag } from "../../../utils/api"; import { dotCMSUtils } from "../../../utils/dotCMSUtils"; const contentTypeName = faker.lorem.word().toLocaleLowerCase(); test.beforeEach("Navigate to content types", async ({ page, request }) => { - const cmsUtils = new dotCMSUtils(); - const listingContentTypesPage = new ListingContentTypesPage(page); + const listingContentTypesPage = new ListingContentTypesPage(page, request); const contentTypeFormPage = new ContentTypeFormPage(page); // Get the username and password from the environment variables @@ -18,37 +16,22 @@ test.beforeEach("Navigate to content types", async ({ page, request }) => { const password = process.env.PASSWORD as string; // Login to dotCMS + const cmsUtils = new dotCMSUtils(); await cmsUtils.login(page, username, password); - + + await listingContentTypesPage.toggleNewContentEditor(true); await listingContentTypesPage.goToUrl(); await listingContentTypesPage.addNewContentType(contentTypeName); await contentTypeFormPage.fillNewContentType(); await listingContentTypesPage.goToUrl(); await listingContentTypesPage.goToAddNewContentType(contentTypeName); - - await updateFeatureFlag(request, { - key: "DOT_FEATURE_FLAG_NEW_EDIT_PAGE", - value: true, - }); - await updateFeatureFlag(request, { - key: "DOT_CONTENT_EDITOR2_ENABLED", - value: true, - }); }); test.afterEach(async ({ page, request }) => { - const listingContentTypesPage = new ListingContentTypesPage(page); + const listingContentTypesPage = new ListingContentTypesPage(page, request); await listingContentTypesPage.goToUrl(); await listingContentTypesPage.deleteContentType(contentTypeName); - - await updateFeatureFlag(request, { - key: "DOT_FEATURE_FLAG_NEW_EDIT_PAGE", - value: false, - }); - await updateFeatureFlag(request, { - key: "DOT_CONTENT_EDITOR2_ENABLED", - value: false, - }); + await listingContentTypesPage.toggleNewContentEditor(false); }); test.describe("text field", () => { From f9dc0b37adda8fa5111f7fa8057b52e67b1285a1 Mon Sep 17 00:00:00 2001 From: Nicolas Molina Date: Thu, 16 Jan 2025 16:50:40 -0400 Subject: [PATCH 23/25] chore(e2e): wait response --- .../frontend/pages/newEditContentForm.page.ts | 12 ++++++++++-- .../tests/newEditContent/fields/textField.spec.ts | 3 ++- 2 files changed, 12 insertions(+), 3 deletions(-) diff --git a/e2e/dotcms-e2e-node/frontend/pages/newEditContentForm.page.ts b/e2e/dotcms-e2e-node/frontend/pages/newEditContentForm.page.ts index cb82d82f3c73..f406e2b2560b 100644 --- a/e2e/dotcms-e2e-node/frontend/pages/newEditContentForm.page.ts +++ b/e2e/dotcms-e2e-node/frontend/pages/newEditContentForm.page.ts @@ -1,4 +1,4 @@ -import { Page } from "@playwright/test"; +import { expect, Page } from "@playwright/test"; export class NewEditContentFormPage { constructor(private page: Page) {} @@ -9,6 +9,14 @@ export class NewEditContentFormPage { async save() { await this.page.getByRole("button", { name: "Save" }).click(); - await this.page.waitForResponse("**/api/v1/workflow/actions/**"); + const response = await this.page.waitForResponse("**/api/v1/workflow/actions/**"); + const jsonData = await response.json(); + + expect(jsonData.entity.inode).not.toBeNull(); + return jsonData.entity.inode; + } + + async goToContent(id: string) { + await this.page.goto(`/dotAdmin/#/content/${id}`); } } diff --git a/e2e/dotcms-e2e-node/frontend/tests/newEditContent/fields/textField.spec.ts b/e2e/dotcms-e2e-node/frontend/tests/newEditContent/fields/textField.spec.ts index d7347955ef17..da5848389118 100644 --- a/e2e/dotcms-e2e-node/frontend/tests/newEditContent/fields/textField.spec.ts +++ b/e2e/dotcms-e2e-node/frontend/tests/newEditContent/fields/textField.spec.ts @@ -45,7 +45,8 @@ test.describe("text field", () => { const textFieldValue = faker.lorem.word(); await newEditContentFormPage.fillTextField(textFieldValue); - await newEditContentFormPage.save(); + const contentId = await newEditContentFormPage.save(); + await newEditContentFormPage.goToContent(contentId); await expect(locatorField).toHaveValue(textFieldValue); }); From 7866842d7283dbfa6c9a3bb388324bbae6140da1 Mon Sep 17 00:00:00 2001 From: Nicolas Molina Date: Thu, 16 Jan 2025 16:50:50 -0400 Subject: [PATCH 24/25] chore(e2e): wait response --- .../frontend/pages/listingContentTypes.pages.ts | 5 ++++- .../frontend/pages/newEditContentForm.page.ts | 4 +++- .../frontend/tests/newEditContent/fields/textField.spec.ts | 2 +- 3 files changed, 8 insertions(+), 3 deletions(-) diff --git a/e2e/dotcms-e2e-node/frontend/pages/listingContentTypes.pages.ts b/e2e/dotcms-e2e-node/frontend/pages/listingContentTypes.pages.ts index 461b8fe2b807..4ce68726b4f6 100644 --- a/e2e/dotcms-e2e-node/frontend/pages/listingContentTypes.pages.ts +++ b/e2e/dotcms-e2e-node/frontend/pages/listingContentTypes.pages.ts @@ -2,7 +2,10 @@ import { APIRequestContext, Page } from "@playwright/test"; import { updateFeatureFlag } from "../utils/api"; export class ListingContentTypesPage { - constructor(private page: Page, private request: APIRequestContext) {} + constructor( + private page: Page, + private request: APIRequestContext, + ) {} async goToUrl() { await this.page.goto("/dotAdmin/#/content-types-angular"); diff --git a/e2e/dotcms-e2e-node/frontend/pages/newEditContentForm.page.ts b/e2e/dotcms-e2e-node/frontend/pages/newEditContentForm.page.ts index f406e2b2560b..d4e395dc1c55 100644 --- a/e2e/dotcms-e2e-node/frontend/pages/newEditContentForm.page.ts +++ b/e2e/dotcms-e2e-node/frontend/pages/newEditContentForm.page.ts @@ -9,7 +9,9 @@ export class NewEditContentFormPage { async save() { await this.page.getByRole("button", { name: "Save" }).click(); - const response = await this.page.waitForResponse("**/api/v1/workflow/actions/**"); + const response = await this.page.waitForResponse( + "**/api/v1/workflow/actions/**", + ); const jsonData = await response.json(); expect(jsonData.entity.inode).not.toBeNull(); diff --git a/e2e/dotcms-e2e-node/frontend/tests/newEditContent/fields/textField.spec.ts b/e2e/dotcms-e2e-node/frontend/tests/newEditContent/fields/textField.spec.ts index da5848389118..98f6ce4fea17 100644 --- a/e2e/dotcms-e2e-node/frontend/tests/newEditContent/fields/textField.spec.ts +++ b/e2e/dotcms-e2e-node/frontend/tests/newEditContent/fields/textField.spec.ts @@ -18,7 +18,7 @@ test.beforeEach("Navigate to content types", async ({ page, request }) => { // Login to dotCMS const cmsUtils = new dotCMSUtils(); await cmsUtils.login(page, username, password); - + await listingContentTypesPage.toggleNewContentEditor(true); await listingContentTypesPage.goToUrl(); await listingContentTypesPage.addNewContentType(contentTypeName); From 7c7ab26558fafb4b4ec540dc96d198f76e358774 Mon Sep 17 00:00:00 2001 From: Nicolas Molina Date: Thu, 16 Jan 2025 18:24:05 -0400 Subject: [PATCH 25/25] chore(e2e): fix error --- .../frontend/pages/listngContent.page.ts | 18 +++++++++++++++--- .../frontend/pages/newEditContentForm.page.ts | 9 +-------- .../newEditContent/fields/textField.spec.ts | 8 +++++--- 3 files changed, 21 insertions(+), 14 deletions(-) diff --git a/e2e/dotcms-e2e-node/frontend/pages/listngContent.page.ts b/e2e/dotcms-e2e-node/frontend/pages/listngContent.page.ts index 74abbd30b503..1d68bef6c85f 100644 --- a/e2e/dotcms-e2e-node/frontend/pages/listngContent.page.ts +++ b/e2e/dotcms-e2e-node/frontend/pages/listngContent.page.ts @@ -8,17 +8,29 @@ export class ListingContentPage { ); async goTo(filter?: string) { - const urlPath = new URL("/c/content", this.page.url()); + const urlPath = "/dotAdmin/#c/content"; + const urlParams = new URLSearchParams(); if (filter) { - urlPath.searchParams.set("filter", filter); + urlParams.set("filter", filter); } - await this.page.goto(urlPath.toString()); + await this.page.goto(`${urlPath}?${urlParams.toString()}`); } async clickAddNewContent() { await this.#addBtn.click(); await this.#addNewContent.click(); } + + async clickFirstContentRow() { + await this.page + .locator('iframe[name="detailFrame"]') + .contentFrame() + .locator("#results_table") + .locator("tr") + .nth(1) + .getByRole("link") + .click(); + } } diff --git a/e2e/dotcms-e2e-node/frontend/pages/newEditContentForm.page.ts b/e2e/dotcms-e2e-node/frontend/pages/newEditContentForm.page.ts index d4e395dc1c55..9cc180c1efbf 100644 --- a/e2e/dotcms-e2e-node/frontend/pages/newEditContentForm.page.ts +++ b/e2e/dotcms-e2e-node/frontend/pages/newEditContentForm.page.ts @@ -1,4 +1,4 @@ -import { expect, Page } from "@playwright/test"; +import { Page } from "@playwright/test"; export class NewEditContentFormPage { constructor(private page: Page) {} @@ -9,13 +9,6 @@ export class NewEditContentFormPage { async save() { await this.page.getByRole("button", { name: "Save" }).click(); - const response = await this.page.waitForResponse( - "**/api/v1/workflow/actions/**", - ); - const jsonData = await response.json(); - - expect(jsonData.entity.inode).not.toBeNull(); - return jsonData.entity.inode; } async goToContent(id: string) { diff --git a/e2e/dotcms-e2e-node/frontend/tests/newEditContent/fields/textField.spec.ts b/e2e/dotcms-e2e-node/frontend/tests/newEditContent/fields/textField.spec.ts index 98f6ce4fea17..55edb77c8bfc 100644 --- a/e2e/dotcms-e2e-node/frontend/tests/newEditContent/fields/textField.spec.ts +++ b/e2e/dotcms-e2e-node/frontend/tests/newEditContent/fields/textField.spec.ts @@ -3,6 +3,7 @@ import { faker } from "@faker-js/faker"; import { ListingContentTypesPage } from "../../../pages/listingContentTypes.pages"; import { ContentTypeFormPage } from "../../../pages/contentTypeForm.page"; import { NewEditContentFormPage } from "../../../pages/newEditContentForm.page"; +import { ListingContentPage } from "../../../pages/listngContent.page"; import { dotCMSUtils } from "../../../utils/dotCMSUtils"; const contentTypeName = faker.lorem.word().toLocaleLowerCase(); @@ -37,7 +38,7 @@ test.afterEach(async ({ page, request }) => { test.describe("text field", () => { test("should save a text field", async ({ page }) => { const newEditContentFormPage = new NewEditContentFormPage(page); - + const listingContentPage = new ListingContentPage(page); const locatorField = page.getByTestId("textField"); await expect(locatorField).toBeVisible(); @@ -45,8 +46,9 @@ test.describe("text field", () => { const textFieldValue = faker.lorem.word(); await newEditContentFormPage.fillTextField(textFieldValue); - const contentId = await newEditContentFormPage.save(); - await newEditContentFormPage.goToContent(contentId); + await newEditContentFormPage.save(); + await listingContentPage.goTo(contentTypeName); + await listingContentPage.clickFirstContentRow(); await expect(locatorField).toHaveValue(textFieldValue); });