diff --git a/e2e/dotcms-e2e-node/frontend/.env b/e2e/dotcms-e2e-node/frontend/.env index e5fb472550f5..7da71e2d3fa8 100644 --- a/e2e/dotcms-e2e-node/frontend/.env +++ b/e2e/dotcms-e2e-node/frontend/.env @@ -2,9 +2,11 @@ CI=false DEV=false BASE_URL=http://localhost:8080 HEADLESS=false -RETRIES=0 +RETRIES=1 +WORKERS=1 REUSE_SERVER=false INCLUDE_HTML=true +TIMEOUT=30000 USERNAME=admin@dotcms.com PASSWORD=admin diff --git a/e2e/dotcms-e2e-node/frontend/.env.ci b/e2e/dotcms-e2e-node/frontend/.env.ci index 18974e89fee2..b8f1e253ff19 100644 --- a/e2e/dotcms-e2e-node/frontend/.env.ci +++ b/e2e/dotcms-e2e-node/frontend/.env.ci @@ -1,5 +1,5 @@ CI=true HEADLESS=true -RETRIES=1 +RETRIES=3 INCLUDE_HTML=false -REUSE_SERVER=true \ No newline at end of file +REUSE_SERVER=true diff --git a/e2e/dotcms-e2e-node/frontend/playwright.config.ts b/e2e/dotcms-e2e-node/frontend/playwright.config.ts index d2594862903d..9c8061750876 100644 --- a/e2e/dotcms-e2e-node/frontend/playwright.config.ts +++ b/e2e/dotcms-e2e-node/frontend/playwright.config.ts @@ -49,9 +49,9 @@ export default defineConfig({ /* Retry on CI only */ retries: parseInt(process.env.RETRIES), /* Opt out of parallel tests on CI. */ - workers: process.env.CI ? 1 : undefined, + workers: parseInt(process.env.WORKERS), /* Reporter to use. See https://playwright.dev/docs/test-reporters */ - timeout: 30000, + timeout: parseInt(process.env.TIMEOUT), reporter, /* Shared settings for all the projects below. See https://playwright.dev/docs/api/class-testoptions. */ 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 0bbd5db94c02..25861a57fffa 100644 --- a/e2e/dotcms-e2e-node/frontend/tests/contentSearch/portletIntegrity.spec.ts +++ b/e2e/dotcms-e2e-node/frontend/tests/contentSearch/portletIntegrity.spec.ts @@ -1,5 +1,5 @@ import {expect, test} from '@playwright/test'; -import {dotCMSUtils} from '../../utils/dotCMSUtils'; +import {dotCMSUtils, waitForVisibleAndCallback} from '../../utils/dotCMSUtils'; import {ContentUtils} from '../../utils/contentUtils'; import {addContent, iFramesLocators, richText} from '../../locators/globalLocators'; import { @@ -30,6 +30,9 @@ test.beforeEach('Navigate to content portlet', async ({page}) => { 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')); + await expect(page.locator('p-breadcrumb')).toContainText('Search All'); }); @@ -151,16 +154,20 @@ test("Validate the clear button in the search filter", async ({page}) => { const iframe = page.frameLocator(iFramesLocators.main_iframe); //expand the search filter - await iframe.getByRole('link', {name: 'Advanced'}).click(); + const advancedLinkLocator = iframe.getByRole('link', {name: 'Advanced'}); + await waitForVisibleAndCallback(advancedLinkLocator, () => advancedLinkLocator.click()); // Select the workflow in the search filter await iframe.locator('#widget_scheme_id [data-dojo-attach-point="_buttonNode"]').click(); await iframe.getByRole('option', {name: 'System Workflow'}).click(); + //TODO remove this await page.waitForTimeout(1000); // Select the step in the search filter - await iframe.locator('div[id=\'widget_step_id\'] div[data-dojo-attach-point=\'_buttonNode\']').click(); - await iframe.getByRole('option', {name: 'New'}).click(); + const widgetStepIdLocator = iframe.locator('div[id=\'widget_step_id\'] div[data-dojo-attach-point=\'_buttonNode\']'); + await waitForVisibleAndCallback(widgetStepIdLocator, () => widgetStepIdLocator.click()); + const newOption = iframe.getByRole('option', {name: 'New'}); + await waitForVisibleAndCallback(newOption, () => newOption.click()); // Select the Show in the search filter await iframe.locator('#widget_showingSelect [data-dojo-attach-point="_buttonNode"]').click(); @@ -183,7 +190,8 @@ test('Validate the hide button collapse the filter', async ({page}) => { const iframe = page.frameLocator(iFramesLocators.main_iframe); await expect(iframe.getByRole('button', {name: 'Search'})).toBeVisible(); - await iframe.getByRole('link', {name: 'Advanced'}).click(); + let advancedLinkLocator = iframe.getByRole('link', {name: 'Advanced'}); + await waitForVisibleAndCallback(advancedLinkLocator, () => advancedLinkLocator.click()); await page.waitForTimeout(1000); await expect(iframe.getByRole('link', {name: 'Advanced'})).not.toBeVisible(); @@ -195,5 +203,3 @@ test('Validate the hide button collapse the filter', async ({page}) => { // Validate the filter has been collapsed await expect(iframe.getByRole('link', {name: 'Advanced'})).toBeVisible(); }); - - diff --git a/e2e/dotcms-e2e-node/frontend/tests/login/translations.spec.ts b/e2e/dotcms-e2e-node/frontend/tests/login/translations.spec.ts index a09148114b1b..bdccbf35de41 100644 --- a/e2e/dotcms-e2e-node/frontend/tests/login/translations.spec.ts +++ b/e2e/dotcms-e2e-node/frontend/tests/login/translations.spec.ts @@ -1,5 +1,6 @@ import {expect, test} from '@playwright/test'; import {assert} from 'console'; +import {waitForVisibleAndCallback} from "../../utils/dotCMSUtils"; const languages = [ {language: 'español (España)', translation: '¡Bienvenido!'}, @@ -20,8 +21,11 @@ languages.forEach(list => { const {language, translation} = list; await page.goto('/dotAdmin'); - await page.getByLabel('dropdown trigger').click(); - await page.getByText(language).click(); + const dropdownTriggerLocator = page.getByLabel('dropdown trigger'); + await waitForVisibleAndCallback(dropdownTriggerLocator, () => dropdownTriggerLocator.click()); + + const pageByTextLocator = page.getByText(language); + await waitForVisibleAndCallback(pageByTextLocator, () => pageByTextLocator.click()); // Assertion of the translation assert(await expect(page.getByTestId('header')).toContainText(translation)); diff --git a/e2e/dotcms-e2e-node/frontend/utils/contentUtils.ts b/e2e/dotcms-e2e-node/frontend/utils/contentUtils.ts index 27417a82cb5c..b0e6d154f25f 100644 --- a/e2e/dotcms-e2e-node/frontend/utils/contentUtils.ts +++ b/e2e/dotcms-e2e-node/frontend/utils/contentUtils.ts @@ -1,6 +1,6 @@ import {Page, expect, FrameLocator} from '@playwright/test'; import { iFramesLocators, richText } from '../locators/globalLocators'; - +import { waitForVisibleAndCallback} from './dotCMSUtils'; export class ContentUtils { page: Page; @@ -17,7 +17,9 @@ export class ContentUtils { */ async fillRichTextForm(page: Page, title: string, body: string, action: string) { const dotIframe = page.frameLocator(iFramesLocators.dot_iframe); - await expect.soft( page.getByRole('heading')).toContainText(richText.label); + + const headingLocator = page.getByRole('heading'); + await waitForVisibleAndCallback(headingLocator, () => expect.soft(headingLocator).toContainText(richText.label)); //Fill title await dotIframe.locator('#title').fill(title); @@ -29,11 +31,11 @@ export class ContentUtils { await dotIframe.getByText(action).click(); //Wait for the content to be saved - await expect(dotIframe.getByText('Content saved')).toBeVisible({ timeout: 9000 }); await expect(dotIframe.getByText('Content saved')).toBeHidden(); //Click on close - await page.getByTestId('close-button').getByRole('button').click(); + const closeBtnLocator = page.getByTestId('close-button').getByRole('button'); + await waitForVisibleAndCallback(closeBtnLocator, () => closeBtnLocator.click()); } /** @@ -44,15 +46,22 @@ export class ContentUtils { */ async addNewContentAction(page: Page, typeLocator: string, typeString: string) { const iframe = page.frameLocator(iFramesLocators.main_iframe); - await expect(iframe.locator('#structure_inode')).toBeVisible(); + const structureINodeLocator = iframe.locator('#structure_inode'); + await waitForVisibleAndCallback(structureINodeLocator, () => expect(structureINodeLocator).toBeVisible()); + //TODO remove this await page.waitForTimeout(1000); - await iframe.locator('#widget_structure_inode div').first().click(); + const structureINodeDivLocator = iframe.locator('#widget_structure_inode div').first(); + await waitForVisibleAndCallback(structureINodeDivLocator, () => structureINodeDivLocator.click()); + //TODO remove this await page.waitForTimeout(1000); - await iframe.getByText(typeLocator).click(); + const typeLocatorByTextLocator = iframe.getByText(typeLocator); + await waitForVisibleAndCallback(typeLocatorByTextLocator, () => typeLocatorByTextLocator.click()); + await iframe.locator('#dijit_form_DropDownButton_0').click(); await expect(iframe.getByLabel('actionPrimaryMenu')).toBeVisible(); await iframe.getByLabel('▼').getByText('Add New Content').click(); - await expect(page.getByRole('heading')).toHaveText(typeString); + const headingLocator = page.getByRole('heading'); + await waitForVisibleAndCallback(headingLocator, () => expect(headingLocator).toHaveText(typeString)); }; /** @@ -74,7 +83,8 @@ export class ContentUtils { * @param iframe */ async showQuery(iframe : FrameLocator) { - await iframe.getByRole('button', { name: 'createOptions' }).click(); + 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(); diff --git a/e2e/dotcms-e2e-node/frontend/utils/dotCMSUtils.ts b/e2e/dotcms-e2e-node/frontend/utils/dotCMSUtils.ts index 80d3c5ec1ab8..6138a5702d55 100644 --- a/e2e/dotcms-e2e-node/frontend/utils/dotCMSUtils.ts +++ b/e2e/dotcms-e2e-node/frontend/utils/dotCMSUtils.ts @@ -1,9 +1,22 @@ import { Page, expect, Locator } from '@playwright/test'; import { loginLocators } from '../locators/globalLocators'; +export const waitFor = async (locator: Locator, state: "attached" | "detached" | "visible" | "hidden"): Promise => { + await locator.waitFor({state: state}); +} + +export const waitForAndCallback = async (locator: Locator, state: "attached" | "detached" | "visible" | "hidden", callback: () => Promise): Promise => { + await waitFor(locator, state); + await callback(); +}; + +export const waitForVisibleAndCallback = async (locator: Locator, callback: () => Promise): Promise => { + await waitForAndCallback(locator, 'visible', callback); +}; + export class dotCMSUtils { page: Page; - + /** * Login to dotCMS * @param page @@ -14,9 +27,11 @@ export class dotCMSUtils { await page.goto('/dotAdmin'); await page.waitForLoadState() await page.fill(loginLocators.userNameInput, username); - await page.fill(loginLocators.passwordInput, password); - await page.getByTestId(loginLocators.loginBtn).click(); - await expect(page.getByRole('link', { name: 'Getting Started' })).toBeVisible(); + await page.fill(loginLocators.passwordInput, 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()); } /** diff --git a/e2e/dotcms-e2e-node/pom.xml b/e2e/dotcms-e2e-node/pom.xml index 4a8a74cc34c8..7aea23a6a22f 100644 --- a/e2e/dotcms-e2e-node/pom.xml +++ b/e2e/dotcms-e2e-node/pom.xml @@ -20,8 +20,7 @@ false install --frozen-lockfile global add playwright - playwright install - playwright install-deps + playwright install --with-deps true local ./frontend @@ -85,18 +84,6 @@ ${playwright.install.cmd} - - install-playwright-deps - - yarn - - generate-resources - - ${e2e.frontend.dir} - ${e2e.test.skip} - ${playwright.install.deps.cmd} - - run node script