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 diff --git a/e2e/dotcms-e2e-node/frontend/locators/globalLocators.ts b/e2e/dotcms-e2e-node/frontend/locators/globalLocators.ts index 93d077396f00..1aa3fb4c485b 100644 --- a/e2e/dotcms-e2e-node/frontend/locators/globalLocators.ts +++ b/e2e/dotcms-e2e-node/frontend/locators/globalLocators.ts @@ -2,49 +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" -} - -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 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/package.json b/e2e/dotcms-e2e-node/frontend/package.json index 96d91ef579b3..09ea8f601dcd 100644 --- a/e2e/dotcms-e2e-node/frontend/package.json +++ b/e2e/dotcms-e2e-node/frontend/package.json @@ -6,6 +6,7 @@ "devDependencies": { "@axe-core/playwright": "^4.10.1", "@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", @@ -28,6 +29,8 @@ "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 .", 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.pages.ts b/e2e/dotcms-e2e-node/frontend/pages/listingContentTypes.pages.ts new file mode 100644 index 000000000000..4ce68726b4f6 --- /dev/null +++ b/e2e/dotcms-e2e-node/frontend/pages/listingContentTypes.pages.ts @@ -0,0 +1,74 @@ +import { APIRequestContext, Page } from "@playwright/test"; +import { updateFeatureFlag } from "../utils/api"; + +export class ListingContentTypesPage { + 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(); + 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 goToAddNewContentType(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(); + } + + 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/pages/listngContent.page.ts b/e2e/dotcms-e2e-node/frontend/pages/listngContent.page.ts new file mode 100644 index 000000000000..1d68bef6c85f --- /dev/null +++ b/e2e/dotcms-e2e-node/frontend/pages/listngContent.page.ts @@ -0,0 +1,36 @@ +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 = "/dotAdmin/#c/content"; + const urlParams = new URLSearchParams(); + + if (filter) { + urlParams.set("filter", filter); + } + + 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 new file mode 100644 index 000000000000..9cc180c1efbf --- /dev/null +++ b/e2e/dotcms-e2e-node/frontend/pages/newEditContentForm.page.ts @@ -0,0 +1,17 @@ +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(); + } + + async goToContent(id: string) { + await this.page.goto(`/dotAdmin/#/content/${id}`); + } +} 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..ad7d8e2487b1 --- /dev/null +++ b/e2e/dotcms-e2e-node/frontend/pages/textField.page.ts @@ -0,0 +1,10 @@ +import { Page } from "@playwright/test"; + +export class TextFieldPage { + constructor(private page: Page) {} + + async fill(variableName: string, value: string) { + const input = this.page.locator(`input#${variableName}`); + await input.fill(value); + } +} diff --git a/e2e/dotcms-e2e-node/frontend/tests/contentSearch/contentData.ts b/e2e/dotcms-e2e-node/frontend/tests/contentSearch/contentData.ts index 62659af34d3f..d678a7d84a05 100644 --- a/e2e/dotcms-e2e-node/frontend/tests/contentSearch/contentData.ts +++ b/e2e/dotcms-e2e-node/frontend/tests/contentSearch/contentData.ts @@ -1,55 +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 4f754144460f..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); }); /** 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 bffe34a3d139..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(); 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/newEditContent/fields/textField.spec.ts b/e2e/dotcms-e2e-node/frontend/tests/newEditContent/fields/textField.spec.ts new file mode 100644 index 000000000000..55edb77c8bfc --- /dev/null +++ b/e2e/dotcms-e2e-node/frontend/tests/newEditContent/fields/textField.spec.ts @@ -0,0 +1,55 @@ +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 { ListingContentPage } from "../../../pages/listngContent.page"; +import { dotCMSUtils } from "../../../utils/dotCMSUtils"; + +const contentTypeName = faker.lorem.word().toLocaleLowerCase(); + +test.beforeEach("Navigate to content types", async ({ page, request }) => { + const listingContentTypesPage = new ListingContentTypesPage(page, request); + const contentTypeFormPage = new ContentTypeFormPage(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 + 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); +}); + +test.afterEach(async ({ page, request }) => { + const listingContentTypesPage = new ListingContentTypesPage(page, request); + await listingContentTypesPage.goToUrl(); + await listingContentTypesPage.deleteContentType(contentTypeName); + await listingContentTypesPage.toggleNewContentEditor(false); +}); + +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(); + + const textFieldValue = faker.lorem.word(); + + await newEditContentFormPage.fillTextField(textFieldValue); + await newEditContentFormPage.save(); + await listingContentPage.goTo(contentTypeName); + await listingContentPage.clickFirstContentRow(); + + await expect(locatorField).toHaveValue(textFieldValue); + }); +}); 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/api.ts b/e2e/dotcms-e2e-node/frontend/utils/api.ts new file mode 100644 index 000000000000..f619f0827bd0 --- /dev/null +++ b/e2e/dotcms-e2e-node/frontend/utils/api.ts @@ -0,0 +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, + 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}`; +} diff --git a/e2e/dotcms-e2e-node/frontend/utils/contentUtils.ts b/e2e/dotcms-e2e-node/frontend/utils/contentUtils.ts index bfd0d236d270..c77d18f9b6aa 100644 --- a/e2e/dotcms-e2e-node/frontend/utils/contentUtils.ts +++ b/e2e/dotcms-e2e-node/frontend/utils/contentUtils.ts @@ -1,407 +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(); + await waitForVisibleAndCallback(dotIframe.locator("#title"), async () => { + await dotIframe.locator("#title").fill(title); + }); - 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; + 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); +}; diff --git a/e2e/dotcms-e2e-node/frontend/yarn.lock b/e2e/dotcms-e2e-node/frontend/yarn.lock index 71d43ef181ef..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== @@ -70,6 +70,11 @@ "@eslint/core" "^0.10.0" 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" @@ -106,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== @@ -143,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== @@ -158,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== @@ -229,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== @@ -268,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== @@ -368,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== @@ -435,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== @@ -891,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== @@ -1057,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==