diff --git a/e2e/dotcms-e2e-node/README.md b/e2e/dotcms-e2e-node/README.md index 53980f712402..3d369f491cc9 100644 --- a/e2e/dotcms-e2e-node/README.md +++ b/e2e/dotcms-e2e-node/README.md @@ -80,7 +80,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 - "scripts": {yarn playwright show-report; + "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", "start-local": "CURRENT_ENV=local yarn run start", @@ -99,6 +99,7 @@ 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 So, assuming that you are a frontend developer and you are still implementing a feature using the `start-dev` script will make much sense since it will run `nx` to start the app in the `4200` port. + ```shell yarn run start-dev ``` diff --git a/e2e/dotcms-e2e-node/frontend/eslint.config.mjs b/e2e/dotcms-e2e-node/frontend/eslint.config.mjs new file mode 100644 index 000000000000..48c52b9b8088 --- /dev/null +++ b/e2e/dotcms-e2e-node/frontend/eslint.config.mjs @@ -0,0 +1,12 @@ +import eslint from "@eslint/js"; +import tseslint from "typescript-eslint"; +import eslintConfigPrettier from "eslint-config-prettier"; +import playwright from "eslint-plugin-playwright"; + +export default tseslint.config( + eslint.configs.recommended, + tseslint.configs.strict, + tseslint.configs.stylistic, + eslintConfigPrettier, + playwright.configs["flat/recommended"], +); diff --git a/e2e/dotcms-e2e-node/frontend/index.js b/e2e/dotcms-e2e-node/frontend/index.js index 43d34115bc7d..75c414107b6d 100644 --- a/e2e/dotcms-e2e-node/frontend/index.js +++ b/e2e/dotcms-e2e-node/frontend/index.js @@ -1,55 +1,59 @@ -const fs = require('fs'); -const path = require('path'); -const xml2js = require('xml2js'); +const fs = require("fs"); +const path = require("path"); +const xml2js = require("xml2js"); const summaryResults = { - completed: 0, - errors: 0, - failures: 0, - skipped: 0 + completed: 0, + errors: 0, + failures: 0, + skipped: 0, }; // Default configurations -const defaultE2eTestsResultsDir = '../target/failsafe-reports'; // Default results directory +const defaultE2eTestsResultsDir = "../target/failsafe-reports"; // Default results directory // Function to generate failsafe-summary.xml const generateFailsafeSummaryXml = (e2eTestsResultsDir) => { - const builder = new xml2js.Builder(); - const result = summaryResults.failures > 0 ? 255 : 0; - const xmlObj = { - 'failsafe-summary': { - $: { result: result }, - completed: summaryResults.completed, - errors: summaryResults.errors, - failures: summaryResults.failures, - skipped: summaryResults.skipped - } - }; + const builder = new xml2js.Builder(); + const result = summaryResults.failures > 0 ? 255 : 0; + const xmlObj = { + "failsafe-summary": { + $: { result: result }, + completed: summaryResults.completed, + errors: summaryResults.errors, + failures: summaryResults.failures, + skipped: summaryResults.skipped, + }, + }; - if (summaryResults.failures > 0) { - xmlObj['failsafe-summary']['failureMessage'] = 'There are test failures.'; - } + if (summaryResults.failures > 0) { + xmlObj["failsafe-summary"]["failureMessage"] = "There are test failures."; + } - const xml = builder.buildObject(xmlObj); + const xml = builder.buildObject(xmlObj); - fs.writeFileSync(path.join(e2eTestsResultsDir, 'failsafe-summary.xml'), xml, 'utf8'); -} + fs.writeFileSync( + path.join(e2eTestsResultsDir, "failsafe-summary.xml"), + xml, + "utf8", + ); +}; const processTestsResults = async () => { - const parser = new xml2js.Parser(); - const xml = fs.readFileSync(process.env.PLAYWRIGHT_JUNIT_OUTPUT_FILE, 'utf8'); - const xmlDoc = await parser.parseStringPromise(xml); + const parser = new xml2js.Parser(); + const xml = fs.readFileSync(process.env.PLAYWRIGHT_JUNIT_OUTPUT_FILE, "utf8"); + const xmlDoc = await parser.parseStringPromise(xml); - xmlDoc.testsuites.testsuite.forEach((ts) => { - const tests = parseInt(ts.tests || '0'); - const errors = parseInt(ts.errors || '0'); - const failures = parseInt(ts.failures || '0'); - const skipped = parseInt(ts.skipped || '0'); - summaryResults.completed += tests; - summaryResults.errors += errors; - summaryResults.failures += failures; - summaryResults.skipped += skipped; - }); + xmlDoc.testsuites.testsuite.forEach((ts) => { + const tests = parseInt(ts.tests || "0"); + const errors = parseInt(ts.errors || "0"); + const failures = parseInt(ts.failures || "0"); + const skipped = parseInt(ts.skipped || "0"); + summaryResults.completed += tests; + summaryResults.errors += errors; + summaryResults.failures += failures; + summaryResults.skipped += skipped; + }); }; /** @@ -57,28 +61,28 @@ const processTestsResults = async () => { * Parses command line arguments for named parameters. */ async function main() { - const e2eTestsResultsDir = path.resolve(__dirname, defaultE2eTestsResultsDir); - // Ensure results directory exists - if (!fs.existsSync(e2eTestsResultsDir)) { - fs.mkdirSync(e2eTestsResultsDir, { recursive: true }); - } + const e2eTestsResultsDir = path.resolve(__dirname, defaultE2eTestsResultsDir); + // Ensure results directory exists + if (!fs.existsSync(e2eTestsResultsDir)) { + fs.mkdirSync(e2eTestsResultsDir, { recursive: true }); + } - try { - await processTestsResults(); - generateFailsafeSummaryXml(e2eTestsResultsDir); + try { + await processTestsResults(); + generateFailsafeSummaryXml(e2eTestsResultsDir); - if (summaryResults.failures > 0) { - console.error('Some E2E tests failed.'); - process.exit(0); - } - } catch (error) { - console.error('An error occurred:', error); - process.exit(1); + if (summaryResults.failures > 0) { + console.error("Some E2E tests failed."); + process.exit(0); } + } catch (error) { + console.error("An error occurred:", error); + process.exit(1); + } } // Run the main function and handle any errors main().catch((err) => { - console.error('Unhandled error:', err); - process.exit(1); + console.error("Unhandled error:", err); + process.exit(1); }); diff --git a/e2e/dotcms-e2e-node/frontend/locators/globalLocators.ts b/e2e/dotcms-e2e-node/frontend/locators/globalLocators.ts index 29fc3fb12bf5..d9e4a7aca4c0 100644 --- a/e2e/dotcms-e2e-node/frontend/locators/globalLocators.ts +++ b/e2e/dotcms-e2e-node/frontend/locators/globalLocators.ts @@ -2,48 +2,47 @@ * Locators for the iframes in the main page. */ export const iFramesLocators = { - main_iframe: 'iframe[name="detailFrame"]', - dot_iframe: 'dot-iframe-dialog iframe[name="detailFrame"]', - wysiwygFrame: 'iframe[title="Rich Text Area\\. Press ALT-F9 for menu\\. Press ALT-F10 for toolbar\\. Press ALT-0 for help"]', - dataTestId: '[data-testid="iframe"]' -} + main_iframe: 'iframe[name="detailFrame"]', + dot_iframe: 'dot-iframe-dialog iframe[name="detailFrame"]', + wysiwygFrame: + 'iframe[title="Rich Text Area\\. Press ALT-F9 for menu\\. Press ALT-F10 for toolbar\\. Press ALT-0 for help"]', + dataTestId: '[data-testid="iframe"]', +}; /** * Locators for the login functionality. */ export const loginLocators = { - userNameInput: 'input[id="inputtext"]', - passwordInput: 'input[id="password"]', - loginBtn: 'submitButton' -} + userNameInput: 'input[id="inputtext"]', + passwordInput: 'input[id="password"]', + loginBtn: "submitButton", +}; /** * Locators for the Add Content functionality. */ export const addContent = { - addBtn: '#dijit_form_DropDownButton_0', - addNewContentSubMenu: 'Add New Content', - addNewMenuLabel: '▼' -} + addBtn: "#dijit_form_DropDownButton_0", + addNewContentSubMenu: "Add New Content", + addNewMenuLabel: "▼", +}; /** * Locators for the Rich Text functionality. */ export const contentGeneric = { - locator: "articleContent (Generic)", - label: "Content (Generic)" -} + locator: "articleContent (Generic)", + label: "Content (Generic)", +}; export const fileAsset = { - locator: "attach_fileFile Asset", - label: "File Asset" -} + locator: "attach_fileFile Asset", + label: "File Asset", +}; export const pageAsset = { - locator: "descriptionPage", - label: "Page" -} - -export { -} from './navigation/menuLocators'; + locator: "descriptionPage", + label: "Page", +}; +export {} from "./navigation/menuLocators"; diff --git a/e2e/dotcms-e2e-node/frontend/locators/navigation/menuLocators.ts b/e2e/dotcms-e2e-node/frontend/locators/navigation/menuLocators.ts index 34bbedd8d8bc..067b4b5ae472 100644 --- a/e2e/dotcms-e2e-node/frontend/locators/navigation/menuLocators.ts +++ b/e2e/dotcms-e2e-node/frontend/locators/navigation/menuLocators.ts @@ -1,48 +1,49 @@ -import {Page, Locator} from '@playwright/test'; +import { Page, Locator } from "@playwright/test"; export class GroupEntriesLocators { - readonly SITE: Locator; - readonly CONTENT: Locator; - readonly SCHEMA: Locator; - - constructor(page: Page) { - this.SITE = page.getByText('Site', {exact: true}); - this.CONTENT = page.getByRole('complementary').getByText('Content', {exact: true}); - this.SCHEMA = page.getByText('Schema'); - - } + readonly SITE: Locator; + readonly CONTENT: Locator; + readonly SCHEMA: Locator; + + constructor(page: Page) { + this.SITE = page.getByText("Site", { exact: true }); + this.CONTENT = page + .getByRole("complementary") + .getByText("Content", { exact: true }); + this.SCHEMA = page.getByText("Schema"); + } } /** * Locators for the tools in the menu */ export class ToolEntriesLocators { - readonly SEARCH_ALL: Locator; - readonly CONTENT_TYPES: Locator; - readonly CATEGORIES: Locator; - - - constructor(page: Page) { - this.SEARCH_ALL = page.getByRole('link', {name: 'Search All'}); - this.CONTENT_TYPES = page.getByRole('link', {name: 'Content Types'}); - this.CATEGORIES = page.getByRole('link', { name: 'Categories' }); - } + readonly SEARCH_ALL: Locator; + readonly CONTENT_TYPES: Locator; + readonly CATEGORIES: Locator; + + constructor(page: Page) { + this.SEARCH_ALL = page.getByRole("link", { name: "Search All" }); + this.CONTENT_TYPES = page.getByRole("link", { name: "Content Types" }); + this.CATEGORIES = page.getByRole("link", { name: "Categories" }); + } } /** * Locators for the menu entries */ export class MenuEntriesLocators { - readonly EXPAND: Locator; - readonly COLLAPSE: Locator; + readonly EXPAND: Locator; + readonly COLLAPSE: Locator; - constructor(page: Page) { - /*this.EXPAND = page.locator('button[ng-reflect-ng-class="[object Object]"]').first(); + constructor(page: Page) { + /*this.EXPAND = page.locator('button[ng-reflect-ng-class="[object Object]"]').first(); this.COLLAPSE = page.locator('button[ng-reflect-ng-class="[object Object]"]').first(); this.EXPAND = page.locator('button[ng-reflect-ng-class="[object Object]"]'); this.COLLAPSE = page.locator('button[ng-reflect-ng-class="[object Object]"]');*/ - this.EXPAND = page.getByRole('button', { name: '' }); - this.COLLAPSE = page.locator('button[ng-reflect-ng-class="[object Object]"]').first(); - - } -} \ No newline at end of file + this.EXPAND = page.getByRole("button", { name: "" }); + this.COLLAPSE = page + .locator('button[ng-reflect-ng-class="[object Object]"]') + .first(); + } +} diff --git a/e2e/dotcms-e2e-node/frontend/package.json b/e2e/dotcms-e2e-node/frontend/package.json index a0310ae7c7bd..a4a702750148 100644 --- a/e2e/dotcms-e2e-node/frontend/package.json +++ b/e2e/dotcms-e2e-node/frontend/package.json @@ -4,9 +4,17 @@ "main": "index.js", "license": "MIT", "devDependencies": { + "@eslint/js": "^9.17.0", "@playwright/test": "^1.48.2", "@types/node": "^22.5.4", - "dotenv": "^16.4.5" + "@typescript-eslint/eslint-plugin": "^8.19.0", + "dotenv": "^16.4.5", + "eslint": "^9.17.0", + "eslint-config-prettier": "^9.1.0", + "eslint-plugin-playwright": "^2.1.0", + "prettier": "3.4.2", + "typescript": "^5.7.2", + "typescript-eslint": "^8.19.0" }, "dependencies": { "jsdom": "^25.0.1", @@ -18,6 +26,9 @@ "start-local": "CURRENT_ENV=local yarn run start", "start-dev": "CURRENT_ENV=dev yarn run start", "start-ci": "CURRENT_ENV=ci yarn run start", - "post-testing": "PLAYWRIGHT_JUNIT_OUTPUT_FILE='../target/failsafe-reports/TEST-e2e-node-results.xml' node index.js" + "post-testing": "PLAYWRIGHT_JUNIT_OUTPUT_FILE='../target/failsafe-reports/TEST-e2e-node-results.xml' node index.js", + "format": "prettier --write .", + "lint": "eslint .", + "lint:fix": "eslint . --fix" } } diff --git a/e2e/dotcms-e2e-node/frontend/playwright.config.ts b/e2e/dotcms-e2e-node/frontend/playwright.config.ts index 9c8061750876..0faf66b1cf99 100644 --- a/e2e/dotcms-e2e-node/frontend/playwright.config.ts +++ b/e2e/dotcms-e2e-node/frontend/playwright.config.ts @@ -1,38 +1,35 @@ -import * as dotenv from 'dotenv'; +import * as dotenv from "dotenv"; import * as path from "node:path"; -import { defineConfig, devices } from '@playwright/test'; -import {ReporterDescription} from "playwright/types/test"; +import { defineConfig, devices } from "@playwright/test"; +import { ReporterDescription } from "playwright/types/test"; const resolveEnvs = () => { - const envFiles = ['.env']; + const envFiles = [".env"]; - if (process.env.CURRENT_ENV === 'local') { - envFiles.push('.env.local'); - } else if (process.env.CURRENT_ENV === 'ci') { - envFiles.push('.env.ci'); - } else if (process.env.CURRENT_ENV === 'dev') { - envFiles.push('.env.dev'); + if (process.env.CURRENT_ENV === "local") { + envFiles.push(".env.local"); + } else if (process.env.CURRENT_ENV === "ci") { + envFiles.push(".env.ci"); + } else if (process.env.CURRENT_ENV === "dev") { + envFiles.push(".env.dev"); } envFiles.forEach((file) => { dotenv.config({ path: path.resolve(__dirname, file), - override: true + override: true, }); }); }; const resolveReporter = () => { - const reporter: ReporterDescription[] = [ - ['junit'], - ['github'] - ]; - if (!!process.env.INCLUDE_HTML) { - reporter.push(['html']) + const reporter: ReporterDescription[] = [["junit"], ["github"]]; + if (!process.env.INCLUDE_HTML) { + reporter.push(["html"]); } return reporter; -} +}; resolveEnvs(); const reporter = resolveReporter(); @@ -41,7 +38,7 @@ const reporter = resolveReporter(); * See https://playwright.dev/docs/test-configuration. */ export default defineConfig({ - testDir: './tests', + testDir: "./tests", /* Run tests in files in parallel */ fullyParallel: true, /* Fail the build on CI if you accidentally left test.only in the source code. */ @@ -58,14 +55,14 @@ export default defineConfig({ use: { baseURL: process.env.BASE_URL, /* Collect trace when retrying the failed test. See https://playwright.dev/docs/trace-viewer */ - trace: 'on-first-retry', + trace: "on-first-retry", headless: !!process.env.HEADLESS, }, /* Configure projects for major browsers */ projects: [ { - name: 'chromium', - use: { ...devices['Desktop Chrome'] }, + name: "chromium", + use: { ...devices["Desktop Chrome"] }, }, /*{ @@ -79,9 +76,9 @@ export default defineConfig({ },*/ ], webServer: { - command: 'nx serve dotcms-ui', - cwd: '../../../core-web', - url: process.env.BASE_URL + '/dotAdmin', - reuseExistingServer: !!process.env.REUSE_SERVER - } + command: "nx serve dotcms-ui", + cwd: "../../../core-web", + url: process.env.BASE_URL + "/dotAdmin", + reuseExistingServer: !!process.env.REUSE_SERVER, + }, }); diff --git a/e2e/dotcms-e2e-node/frontend/prettier-config.json b/e2e/dotcms-e2e-node/frontend/prettier-config.json new file mode 100644 index 000000000000..912e74373922 --- /dev/null +++ b/e2e/dotcms-e2e-node/frontend/prettier-config.json @@ -0,0 +1,11 @@ +{ + "arrowParens": "always", + "bracketSpacing": true, + "endOfLine": "lf", + "printWidth": 140, + "semi": false, + "singleQuote": true, + "tabWidth": 2, + "trailingComma": "all", + "useTabs": false +} diff --git a/e2e/dotcms-e2e-node/frontend/tests/contentSearch/contentData.ts b/e2e/dotcms-e2e-node/frontend/tests/contentSearch/contentData.ts index 2d801d24918c..b298ef4c9719 100644 --- a/e2e/dotcms-e2e-node/frontend/tests/contentSearch/contentData.ts +++ b/e2e/dotcms-e2e-node/frontend/tests/contentSearch/contentData.ts @@ -1,46 +1,41 @@ -import {Page} from "@playwright/test"; - /** * Content to add a Rich Text content */ export const genericContent1 = { - title: "Automation Test", - body: "This is a sample content", - newTitle : "Automation Test edited", - newBody : "This is a sample content edited" -} + title: "Automation Test", + body: "This is a sample content", + newTitle: "Automation Test edited", + newBody: "This is a sample content edited", +}; /** * Content actions text content locators */ export const contentProperties = { - language: "English (US)", - publishWfAction: "Publish", - unpublishWfAction: "Unpublish", - unlockWfAction: "Unlock", - archiveWfAction: "Archive", - deleteWfAction: "Delete" -} + language: "English (US)", + publishWfAction: "Publish", + unpublishWfAction: "Unpublish", + unlockWfAction: "Unlock", + archiveWfAction: "Archive", + deleteWfAction: "Delete", +}; export const fileAssetContent = { - title: "File Asset title", - body: "This is a sample file asset content", - fromURL:"https://upload.wikimedia.org/wikipedia/commons/0/03/DotCMS-logo.svg", - newFileName:"New file asset.txt", - newFileText:"This is a new file asset content", - host:"default" -} + title: "File Asset title", + body: "This is a sample file asset content", + fromURL: + "https://upload.wikimedia.org/wikipedia/commons/0/03/DotCMS-logo.svg", + newFileName: "New file asset.txt", + newFileText: "This is a new file asset content", + host: "default", +}; export const pageAssetContent = { - title: "PageAsset1", - host: "default", - template: "System Template", - friendlyName: "friendlyName-test", - showOnMenu: true, - sortOrder: "1", - cacheTTL: 0, -} - - - - + title: "PageAsset1", + host: "default", + template: "System Template", + friendlyName: "friendlyName-test", + showOnMenu: true, + sortOrder: "1", + cacheTTL: 0, +}; diff --git a/e2e/dotcms-e2e-node/frontend/tests/contentSearch/contentEditing.spec.ts b/e2e/dotcms-e2e-node/frontend/tests/contentSearch/contentEditing.spec.ts index d9df6e21bf93..017bdf35f432 100644 --- a/e2e/dotcms-e2e-node/frontend/tests/contentSearch/contentEditing.spec.ts +++ b/e2e/dotcms-e2e-node/frontend/tests/contentSearch/contentEditing.spec.ts @@ -1,92 +1,142 @@ -import {expect, test} from '@playwright/test'; -import {dotCMSUtils, waitForVisibleAndCallback} from '../../utils/dotCMSUtils'; +import { expect, test } from "@playwright/test"; import { - GroupEntriesLocators, - MenuEntriesLocators, - ToolEntriesLocators -} from '../../locators/navigation/menuLocators'; -import {ContentUtils} from "../../utils/contentUtils"; -import {iFramesLocators, contentGeneric, fileAsset, pageAsset} from "../../locators/globalLocators"; -import {genericContent1, contentProperties, fileAssetContent, pageAssetContent} from "./contentData"; -import {assert} from "console"; - + dotCMSUtils, + waitForVisibleAndCallback, +} from "../../utils/dotCMSUtils"; +import { + GroupEntriesLocators, + MenuEntriesLocators, + ToolEntriesLocators, +} from "../../locators/navigation/menuLocators"; +import { ContentUtils } from "../../utils/contentUtils"; +import { + iFramesLocators, + contentGeneric, + fileAsset, + pageAsset, +} from "../../locators/globalLocators"; +import { + genericContent1, + contentProperties, + fileAssetContent, + pageAssetContent, +} from "./contentData"; +import { assert } from "console"; /** * Test to navigate to the content portlet and login to the dotCMS instance * @param page */ -test.beforeEach('Navigate to content portlet', async ({page}) => { - const cmsUtils = new dotCMSUtils(); - const menuLocators = new MenuEntriesLocators(page); - const groupsLocators = new GroupEntriesLocators(page); - const toolsLocators = new ToolEntriesLocators(page); - - // Get the username and password from the environment variables - const username = process.env.USERNAME as string; - const password = process.env.PASSWORD as string; - - // Login to dotCMS - await cmsUtils.login(page, username, password); - await cmsUtils.navigate(menuLocators.EXPAND, groupsLocators.CONTENT, toolsLocators.SEARCH_ALL); - - // Validate the portlet title - const breadcrumbLocator = page.locator('p-breadcrumb'); - await waitForVisibleAndCallback(breadcrumbLocator, () => expect(breadcrumbLocator).toContainText('Search All')); +test.beforeEach("Navigate to content portlet", async ({ page }) => { + const cmsUtils = new dotCMSUtils(); + const menuLocators = new MenuEntriesLocators(page); + const groupsLocators = new GroupEntriesLocators(page); + const toolsLocators = new ToolEntriesLocators(page); + + // Get the username and password from the environment variables + const username = process.env.USERNAME as string; + const password = process.env.PASSWORD as string; + + // Login to dotCMS + await cmsUtils.login(page, username, password); + await cmsUtils.navigate( + menuLocators.EXPAND, + groupsLocators.CONTENT, + toolsLocators.SEARCH_ALL, + ); + + // Validate the portlet title + const breadcrumbLocator = page.locator("p-breadcrumb"); + await waitForVisibleAndCallback(breadcrumbLocator, () => + expect(breadcrumbLocator).toContainText("Search All"), + ); }); /** * test to add a new piece of content (generic content) */ -test('Add a new Generic content', async ({page}) => { - const contentUtils = new ContentUtils(page); - const iframe = page.frameLocator(iFramesLocators.main_iframe); - - // Adding new rich text content - await contentUtils.addNewContentAction(page, contentGeneric.locator, contentGeneric.label); - await contentUtils.fillRichTextForm(page, genericContent1.title, genericContent1.body, contentProperties.publishWfAction); - await contentUtils.workflowExecutionValidationAndClose(page, 'Content saved'); - - await waitForVisibleAndCallback(iframe.locator('#results_table tbody tr').first(), async () => {}); - - await contentUtils.validateContentExist(page, genericContent1.title).then(assert); +test("Add a new Generic content", async ({ page }) => { + const contentUtils = new ContentUtils(page); + const iframe = page.frameLocator(iFramesLocators.main_iframe); + + // Adding new rich text content + await contentUtils.addNewContentAction( + page, + contentGeneric.locator, + contentGeneric.label, + ); + await contentUtils.fillRichTextForm( + page, + genericContent1.title, + genericContent1.body, + contentProperties.publishWfAction, + ); + await contentUtils.workflowExecutionValidationAndClose(page, "Content saved"); + + await waitForVisibleAndCallback( + iframe.locator("#results_table tbody tr").first(), + async () => {}, + ); + + await contentUtils + .validateContentExist(page, genericContent1.title) + .then(assert); }); /** * Test to edit an existing piece of content */ -test('Edit a generic content', async ({page}) => { - const contentUtils = new ContentUtils(page); - const iframe = page.frameLocator(iFramesLocators.main_iframe); - - // Edit the content - await contentUtils.selectTypeOnFilter(page, contentGeneric.locator); - await contentUtils.editContent(page, genericContent1.title, genericContent1.newTitle, genericContent1.newBody, contentProperties.publishWfAction); - await waitForVisibleAndCallback(iframe.locator('#results_table tbody tr').first(), async () => {}); - await contentUtils.validateContentExist(page, genericContent1.newTitle).then(assert); +test("Edit a generic content", async ({ page }) => { + const contentUtils = new ContentUtils(page); + const iframe = page.frameLocator(iFramesLocators.main_iframe); + + // Edit the content + await contentUtils.selectTypeOnFilter(page, contentGeneric.locator); + await contentUtils.editContent( + page, + genericContent1.title, + genericContent1.newTitle, + genericContent1.newBody, + contentProperties.publishWfAction, + ); + await waitForVisibleAndCallback( + iframe.locator("#results_table tbody tr").first(), + async () => {}, + ); + await contentUtils + .validateContentExist(page, genericContent1.newTitle) + .then(assert); }); - /** * Test to delete an existing piece of content */ -test('Delete a generic of content', async ({ page }) => { - const contentUtils = new ContentUtils(page); - // Delete the content - await contentUtils.deleteContent(page, genericContent1.newTitle); - } -); +test("Delete a generic of content", async ({ page }) => { + const contentUtils = new ContentUtils(page); + // Delete the content + await contentUtils.deleteContent(page, genericContent1.newTitle); +}); /** * Test to make sure we are validating the required of text fields on the content creation * */ -test('Validate required on text fields', async ({page}) => { - const contentUtils = new ContentUtils(page); - const iframe = page.frameLocator(iFramesLocators.dot_iframe); - - await contentUtils.addNewContentAction(page, contentGeneric.locator, contentGeneric.label); - await contentUtils.fillRichTextForm(page, '', genericContent1.body, contentProperties.publishWfAction); - await expect(iframe.getByText('Error x')).toBeVisible(); - await expect(iframe.getByText('The field Title is required.')).toBeVisible(); +test("Validate required on text fields", async ({ page }) => { + const contentUtils = new ContentUtils(page); + const iframe = page.frameLocator(iFramesLocators.dot_iframe); + + await contentUtils.addNewContentAction( + page, + contentGeneric.locator, + contentGeneric.label, + ); + await contentUtils.fillRichTextForm( + page, + "", + genericContent1.body, + contentProperties.publishWfAction, + ); + await expect(iframe.getByText("Error x")).toBeVisible(); + await expect(iframe.getByText("The field Title is required.")).toBeVisible(); }); /** Please enable after fixing the issue #30748 @@ -107,106 +157,149 @@ test('Validate required on blockContent fields', async ({page}) => { /** * Test to validate you are able to add file assets importing from url */ -test('Validate you are able to add file assets importing from url', async ({page}) => { - const contentUtils = new ContentUtils(page); - - await contentUtils.addNewContentAction(page, fileAsset.locator, fileAsset.label); - const params = { - page: page, - host: fileAssetContent.host, - title: fileAssetContent.title, - action: contentProperties.publishWfAction, - fromURL: fileAssetContent.fromURL - }; - await contentUtils.fillFileAssetForm(params); - await contentUtils.workflowExecutionValidationAndClose(page, 'Content saved'); - await expect(contentUtils.validateContentExist(page, 'DotCMS-logo.svg')).resolves.toBeTruthy(); +test("Validate you are able to add file assets importing from url", async ({ + page, +}) => { + const contentUtils = new ContentUtils(page); + + await contentUtils.addNewContentAction( + page, + fileAsset.locator, + fileAsset.label, + ); + const params = { + page: page, + host: fileAssetContent.host, + title: fileAssetContent.title, + action: contentProperties.publishWfAction, + fromURL: fileAssetContent.fromURL, + }; + await contentUtils.fillFileAssetForm(params); + await contentUtils.workflowExecutionValidationAndClose(page, "Content saved"); + await expect( + contentUtils.validateContentExist(page, "DotCMS-logo.svg"), + ).resolves.toBeTruthy(); }); /** * Test to validate you are able to add file assets creating a new file */ -test('Validate you are able to add file assets creating a new file', async ({page}) => { - const contentUtils = new ContentUtils(page); - - await contentUtils.addNewContentAction(page, fileAsset.locator, fileAsset.label); - const params = { - page: page, - host: fileAssetContent.host, - title: fileAssetContent.title, - action: contentProperties.publishWfAction, - newFileName: fileAssetContent.newFileName, - newFileText: fileAssetContent.newFileText - }; - await contentUtils.fillFileAssetForm(params); - await contentUtils.workflowExecutionValidationAndClose(page, 'Content saved'); - await contentUtils.validateContentExist(page, fileAssetContent.newFileName).then(assert); +test("Validate you are able to add file assets creating a new file", async ({ + page, +}) => { + const contentUtils = new ContentUtils(page); + + await contentUtils.addNewContentAction( + page, + fileAsset.locator, + fileAsset.label, + ); + const params = { + page: page, + host: fileAssetContent.host, + title: fileAssetContent.title, + action: contentProperties.publishWfAction, + newFileName: fileAssetContent.newFileName, + newFileText: fileAssetContent.newFileText, + }; + await contentUtils.fillFileAssetForm(params); + await contentUtils.workflowExecutionValidationAndClose(page, "Content saved"); + await contentUtils + .validateContentExist(page, fileAssetContent.newFileName) + .then(assert); }); /** * Test to validate the required on file asset fields */ -test('Validate the required on file asset fields', async ({page}) => { - const detailsFrame = page.frameLocator(iFramesLocators.dot_iframe); - const contentUtils = new ContentUtils(page); - - await contentUtils.addNewContentAction(page, fileAsset.locator, fileAsset.label); - const params = { - page: page, - host: fileAssetContent.host, - title: fileAssetContent.title, - action: contentProperties.publishWfAction - } - await contentUtils.fillFileAssetForm(params); - await waitForVisibleAndCallback(detailsFrame.getByText('Error x'), async () => {}); - let errorMessage = detailsFrame.getByText('The field File Asset is'); - await waitForVisibleAndCallback(errorMessage, () => expect(errorMessage).toBeVisible()); +test("Validate the required on file asset fields", async ({ page }) => { + const detailsFrame = page.frameLocator(iFramesLocators.dot_iframe); + const contentUtils = new ContentUtils(page); + + await contentUtils.addNewContentAction( + page, + fileAsset.locator, + fileAsset.label, + ); + const params = { + page: page, + host: fileAssetContent.host, + title: fileAssetContent.title, + action: contentProperties.publishWfAction, + }; + await contentUtils.fillFileAssetForm(params); + await waitForVisibleAndCallback( + detailsFrame.getByText("Error x"), + async () => {}, + ); + let errorMessage = detailsFrame.getByText("The field File Asset is"); + await waitForVisibleAndCallback(errorMessage, () => + expect(errorMessage).toBeVisible(), + ); }); /** * Test to validate the auto complete on FileName field accepting the name change */ -test('Validate the auto complete on FileName field accepting change', async ({page}) => { - const detailsFrame = page.frameLocator(iFramesLocators.dot_iframe); - const contentUtils = new ContentUtils(page); - - await contentUtils.addNewContentAction(page, fileAsset.locator, fileAsset.label); - await detailsFrame.locator('#fileName').fill('test'); - const params = { - page: page, - host: fileAssetContent.host, - title: fileAssetContent.title, - newFileName: fileAssetContent.newFileName, - newFileText: fileAssetContent.newFileText - } - await contentUtils.fillFileAssetForm(params); - const replaceText = detailsFrame.getByText('Do you want to replace the'); - await waitForVisibleAndCallback(replaceText, () => expect(replaceText).toBeVisible()); - await detailsFrame.getByLabel('Yes').click(); - await expect(detailsFrame.locator('#fileName')).toHaveValue(fileAssetContent.newFileName); +test("Validate the auto complete on FileName field accepting change", async ({ + page, +}) => { + const detailsFrame = page.frameLocator(iFramesLocators.dot_iframe); + const contentUtils = new ContentUtils(page); + + await contentUtils.addNewContentAction( + page, + fileAsset.locator, + fileAsset.label, + ); + await detailsFrame.locator("#fileName").fill("test"); + const params = { + page: page, + host: fileAssetContent.host, + title: fileAssetContent.title, + newFileName: fileAssetContent.newFileName, + newFileText: fileAssetContent.newFileText, + }; + await contentUtils.fillFileAssetForm(params); + const replaceText = detailsFrame.getByText("Do you want to replace the"); + await waitForVisibleAndCallback(replaceText, () => + expect(replaceText).toBeVisible(), + ); + await detailsFrame.getByLabel("Yes").click(); + await expect(detailsFrame.locator("#fileName")).toHaveValue( + fileAssetContent.newFileName, + ); }); /** * Test to validate the auto complete on FileName field rejecting file name change */ -test('Validate the auto complete on FileName field rejecting change', async ({page}) => { - const detailsFrame = page.frameLocator(iFramesLocators.dot_iframe); - const contentUtils = new ContentUtils(page); - - await contentUtils.addNewContentAction(page, fileAsset.locator, fileAsset.label); - await detailsFrame.locator('#fileName').fill('test'); - const params = { - page: page, - host: fileAssetContent.host, - title: fileAssetContent.title, - newFileName: fileAssetContent.newFileName, - newFileText: fileAssetContent.newFileText - } - await contentUtils.fillFileAssetForm(params); - const replaceText = detailsFrame.getByText('Do you want to replace the'); - await waitForVisibleAndCallback(replaceText, async () => expect(replaceText).toBeVisible()); - await detailsFrame.getByLabel('No').click(); - await expect(detailsFrame.locator('#fileName')).toHaveValue('test'); +test("Validate the auto complete on FileName field rejecting change", async ({ + page, +}) => { + const detailsFrame = page.frameLocator(iFramesLocators.dot_iframe); + const contentUtils = new ContentUtils(page); + + await contentUtils.addNewContentAction( + page, + fileAsset.locator, + fileAsset.label, + ); + await detailsFrame.locator("#fileName").fill("test"); + const params = { + page: page, + host: fileAssetContent.host, + title: fileAssetContent.title, + newFileName: fileAssetContent.newFileName, + newFileText: fileAssetContent.newFileText, + }; + await contentUtils.fillFileAssetForm(params); + const replaceText = detailsFrame.getByText("Do you want to replace the"); + await waitForVisibleAndCallback(replaceText, async () => + expect(replaceText).toBeVisible(), + ); + await detailsFrame.getByLabel("No").click(); + await expect(detailsFrame.locator("#fileName")).toHaveValue("test"); }); /** @@ -228,83 +321,108 @@ test('Validate the title field is not changing in the file asset auto complete', /** * Test to validate you are able to delete a file asset content */ -test('Delete a file asset content', async ({ page }) => { - const contentUtils = new ContentUtils(page); - // Delete the content - await contentUtils.deleteContent(page, fileAssetContent.title); +test("Delete a file asset content", async ({ page }) => { + const contentUtils = new ContentUtils(page); + // Delete the content + await contentUtils.deleteContent(page, fileAssetContent.title); }); /** * Test to validate you are able to add new pages */ -test('Add a new page', async ({page}) => { - const contentUtils = new ContentUtils(page); - const iframe = page.frameLocator(iFramesLocators.main_iframe); - - await contentUtils.addNewContentAction(page, pageAsset.locator, pageAsset.label); - const params = { - page: page, - title: pageAssetContent.title, - host: pageAssetContent.host, - template: pageAssetContent.template, - friendlyName: pageAssetContent.friendlyName, - showOnMenu: pageAssetContent.showOnMenu, - sortOrder: pageAssetContent.sortOrder, - cacheTTL: pageAssetContent.cacheTTL, - action: contentProperties.publishWfAction, - } - await contentUtils.fillPageAssetForm(params); - const dataFrame = page.frameLocator(iFramesLocators.dataTestId); - await waitForVisibleAndCallback(dataFrame.getByRole('banner'), async () => {}); - await expect(page.locator('ol')).toContainText('Pages'+ pageAssetContent.title); +test("Add a new page", async ({ page }) => { + const contentUtils = new ContentUtils(page); + const iframe = page.frameLocator(iFramesLocators.main_iframe); + + await contentUtils.addNewContentAction( + page, + pageAsset.locator, + pageAsset.label, + ); + const params = { + page: page, + title: pageAssetContent.title, + host: pageAssetContent.host, + template: pageAssetContent.template, + friendlyName: pageAssetContent.friendlyName, + showOnMenu: pageAssetContent.showOnMenu, + sortOrder: pageAssetContent.sortOrder, + cacheTTL: pageAssetContent.cacheTTL, + action: contentProperties.publishWfAction, + }; + await contentUtils.fillPageAssetForm(params); + const dataFrame = page.frameLocator(iFramesLocators.dataTestId); + await waitForVisibleAndCallback( + dataFrame.getByRole("banner"), + async () => {}, + ); + await expect(page.locator("ol")).toContainText( + "Pages" + pageAssetContent.title, + ); }); /** * Test to validate the required fields on the page form */ -test('Validate required fields on page asset', async ({page}) => { - const contentUtils = new ContentUtils(page); - const detailFrame = page.frameLocator(iFramesLocators.dot_iframe); - - await contentUtils.addNewContentAction(page, pageAsset.locator, pageAsset.label); - const params = { - page: page, - title: "", - host: pageAssetContent.host, - template: pageAssetContent.template, - showOnMenu: pageAssetContent.showOnMenu, - action: contentProperties.publishWfAction - } - await contentUtils.fillPageAssetForm(params); - await waitForVisibleAndCallback(detailFrame.getByText('Error x'), async () => {}); - - await expect(detailFrame.getByText('The field Title is required.')).toBeVisible(); - await expect(detailFrame.getByText('The field Url is required.')).toBeVisible(); - await expect(detailFrame.getByText('The field Friendly Name is')).toBeVisible(); +test("Validate required fields on page asset", async ({ page }) => { + const contentUtils = new ContentUtils(page); + const detailFrame = page.frameLocator(iFramesLocators.dot_iframe); + + await contentUtils.addNewContentAction( + page, + pageAsset.locator, + pageAsset.label, + ); + const params = { + page: page, + title: "", + host: pageAssetContent.host, + template: pageAssetContent.template, + showOnMenu: pageAssetContent.showOnMenu, + action: contentProperties.publishWfAction, + }; + await contentUtils.fillPageAssetForm(params); + await waitForVisibleAndCallback( + detailFrame.getByText("Error x"), + async () => {}, + ); + + await expect( + detailFrame.getByText("The field Title is required."), + ).toBeVisible(); + await expect( + detailFrame.getByText("The field Url is required."), + ).toBeVisible(); + await expect( + detailFrame.getByText("The field Friendly Name is"), + ).toBeVisible(); }); /** * Test to validate the auto generation of fields on page asset */ -test('Validate auto generation of fields on page asset', async ({page}) => { - const contentUtils = new ContentUtils(page); - const detailFrame = page.frameLocator(iFramesLocators.dot_iframe); - - await contentUtils.addNewContentAction(page, pageAsset.locator, pageAsset.label); - const params = { - page: page, - title: pageAssetContent.title, - host: pageAssetContent.host, - template: pageAssetContent.template, - showOnMenu: pageAssetContent.showOnMenu, - } - await contentUtils.fillPageAssetForm(params); - - await expect(detailFrame.locator('#url')).toHaveValue(pageAssetContent.title.toLowerCase()); - await expect(detailFrame.locator('#friendlyName')).toHaveValue(pageAssetContent.title); +test("Validate auto generation of fields on page asset", async ({ page }) => { + const contentUtils = new ContentUtils(page); + const detailFrame = page.frameLocator(iFramesLocators.dot_iframe); + + await contentUtils.addNewContentAction( + page, + pageAsset.locator, + pageAsset.label, + ); + const params = { + page: page, + title: pageAssetContent.title, + host: pageAssetContent.host, + template: pageAssetContent.template, + showOnMenu: pageAssetContent.showOnMenu, + }; + await contentUtils.fillPageAssetForm(params); + + await expect(detailFrame.locator("#url")).toHaveValue( + pageAssetContent.title.toLowerCase(), + ); + await expect(detailFrame.locator("#friendlyName")).toHaveValue( + pageAssetContent.title, + ); }); - - - - - diff --git a/e2e/dotcms-e2e-node/frontend/tests/contentSearch/portletIntegrity.spec.ts b/e2e/dotcms-e2e-node/frontend/tests/contentSearch/portletIntegrity.spec.ts index 5b9a55722856..ed098be73cde 100644 --- a/e2e/dotcms-e2e-node/frontend/tests/contentSearch/portletIntegrity.spec.ts +++ b/e2e/dotcms-e2e-node/frontend/tests/contentSearch/portletIntegrity.spec.ts @@ -1,204 +1,263 @@ -import {expect, test} from '@playwright/test'; -import {dotCMSUtils, waitForVisibleAndCallback} from '../../utils/dotCMSUtils'; -import {ContentUtils} from '../../utils/contentUtils'; -import {addContent, iFramesLocators, contentGeneric} from '../../locators/globalLocators'; +import { expect, test } from "@playwright/test"; import { - GroupEntriesLocators, - MenuEntriesLocators, - ToolEntriesLocators -} from '../../locators/navigation/menuLocators'; -import {contentProperties, genericContent1} from './contentData'; + dotCMSUtils, + waitForVisibleAndCallback, +} from "../../utils/dotCMSUtils"; +import { ContentUtils } from "../../utils/contentUtils"; +import { + addContent, + iFramesLocators, + contentGeneric, +} from "../../locators/globalLocators"; +import { + GroupEntriesLocators, + MenuEntriesLocators, + ToolEntriesLocators, +} from "../../locators/navigation/menuLocators"; +import { contentProperties, genericContent1 } from "./contentData"; const cmsUtils = new dotCMSUtils(); /** * Test to navigate to the content portlet and login to the dotCMS instance * @param page - */ -test.beforeEach('Navigate to content portlet', async ({page}) => { - // Instance the menu Navigation locators - const menuLocators = new MenuEntriesLocators(page); - const groupsLocators = new GroupEntriesLocators(page); - const toolsLocators = new ToolEntriesLocators(page); - - // Get the username and password from the environment variables - const username = process.env.USERNAME as string; - const password = process.env.PASSWORD as string; - - // Login to dotCMS - await cmsUtils.login(page, username, password); - await cmsUtils.navigate(menuLocators.EXPAND, groupsLocators.CONTENT, toolsLocators.SEARCH_ALL); - - // Validate the portlet title - const breadcrumbLocator = page.locator('p-breadcrumb'); - await waitForVisibleAndCallback(breadcrumbLocator, () => expect(breadcrumbLocator).toContainText('Search All')); + */ +test.beforeEach("Navigate to content portlet", async ({ page }) => { + // Instance the menu Navigation locators + const menuLocators = new MenuEntriesLocators(page); + const groupsLocators = new GroupEntriesLocators(page); + const toolsLocators = new ToolEntriesLocators(page); + + // Get the username and password from the environment variables + const username = process.env.USERNAME as string; + const password = process.env.PASSWORD as string; + + // Login to dotCMS + await cmsUtils.login(page, username, password); + await cmsUtils.navigate( + menuLocators.EXPAND, + groupsLocators.CONTENT, + toolsLocators.SEARCH_ALL, + ); + + // Validate the portlet title + const breadcrumbLocator = page.locator("p-breadcrumb"); + await waitForVisibleAndCallback(breadcrumbLocator, () => + expect(breadcrumbLocator).toContainText("Search All"), + ); }); - /** * Test to validate the portlet title * @param page */ -test('Validate portlet title', async ({page}) => { - await expect(page.locator('p-breadcrumb')).toContainText('Search All'); +test("Validate portlet title", async ({ page }) => { + await expect(page.locator("p-breadcrumb")).toContainText("Search All"); }); - /** * Test to validate the filter is present and usable in the content portlet * @param page - */ -test('Search filter', async ({page}) => { - const contentUtils = new ContentUtils(page); - const iframe = page.frameLocator(iFramesLocators.main_iframe); - - // Adding new rich text content - await contentUtils.addNewContentAction(page, contentGeneric.locator, contentGeneric.label); - await contentUtils.fillRichTextForm(page, genericContent1.title, genericContent1.body, contentProperties.publishWfAction); - await contentUtils.workflowExecutionValidationAndClose(page, 'Content saved'); - - // Validate the content has been created - await expect.soft(iframe.getByRole('link', {name: genericContent1.title}).first()).toBeVisible(); - await iframe.locator('#allFieldTB').fill(genericContent1.title); - await page.keyboard.press('Enter'); - - //validate the search filter is working - await expect(iframe.getByRole('link', {name: genericContent1.title}).first()).toBeVisible(); + */ +test("Search filter", async ({ page }) => { + const contentUtils = new ContentUtils(page); + const iframe = page.frameLocator(iFramesLocators.main_iframe); + + // Adding new rich text content + await contentUtils.addNewContentAction( + page, + contentGeneric.locator, + contentGeneric.label, + ); + await contentUtils.fillRichTextForm( + page, + genericContent1.title, + genericContent1.body, + contentProperties.publishWfAction, + ); + await contentUtils.workflowExecutionValidationAndClose(page, "Content saved"); + + // Validate the content has been created + await expect + .soft(iframe.getByRole("link", { name: genericContent1.title }).first()) + .toBeVisible(); + await iframe.locator("#allFieldTB").fill(genericContent1.title); + await page.keyboard.press("Enter"); + + //validate the search filter is working + await expect( + iframe.getByRole("link", { name: genericContent1.title }).first(), + ).toBeVisible(); }); /** * Test to validate the views are working in the content portlet * @param page - */ -test('Validate views buttons', async ({page}) => { - const iframe = page.frameLocator(iFramesLocators.main_iframe); - - await iframe.getByLabel('List', {exact: true}).isVisible(); - await iframe.getByLabel('Card').isVisible(); - - // Click on the card view - await iframe.getByLabel('Card').click(); - await iframe.locator('.hydrated > dot-contentlet-thumbnail > .hydrated').first().isVisible(); + */ +test("Validate views buttons", async ({ page }) => { + const iframe = page.frameLocator(iFramesLocators.main_iframe); + + await iframe.getByLabel("List", { exact: true }).isVisible(); + await iframe.getByLabel("Card").isVisible(); + + // Click on the card view + await iframe.getByLabel("Card").click(); + await iframe + .locator(".hydrated > dot-contentlet-thumbnail > .hydrated") + .first() + .isVisible(); }); /** * Test to validate the add content button is present and functional * @param page - */ -test('Validate add content button is present and functional', async ({page}) => { - const iframe = page.frameLocator(iFramesLocators.main_iframe); - - await iframe.locator(addContent.addBtn).click(); - await expect(iframe.getByLabel(addContent.addNewMenuLabel).getByText(addContent.addNewContentSubMenu)).toBeVisible(); + */ +test("Validate add content button is present and functional", async ({ + page, +}) => { + const iframe = page.frameLocator(iFramesLocators.main_iframe); + + await iframe.locator(addContent.addBtn).click(); + await expect( + iframe + .getByLabel(addContent.addNewMenuLabel) + .getByText(addContent.addNewContentSubMenu), + ).toBeVisible(); }); - /** * Test to validate the behavior of the bulk Workflow actions button * @param page */ -test('Validate bulk Workflow actions', async ({page}) => { - const iframe = page.frameLocator(iFramesLocators.main_iframe); - - // Validate the button is present and disabled by default - const workflowActionsButton = iframe.getByRole('button', {name: 'Available Workflow Actions'}); - await expect(workflowActionsButton).toBeDisabled(); - - // Check the checkbox and validate the button is enabled - await iframe.locator('#checkbox0').check(); - await expect(workflowActionsButton).toBeEnabled(); - - // Click on the button and validate the dialog is present - await workflowActionsButton.click(); - await expect(iframe.locator('.dijitDialog[role="dialog"]').first()).toBeVisible(); +test("Validate bulk Workflow actions", async ({ page }) => { + const iframe = page.frameLocator(iFramesLocators.main_iframe); + + // Validate the button is present and disabled by default + const workflowActionsButton = iframe.getByRole("button", { + name: "Available Workflow Actions", + }); + await expect(workflowActionsButton).toBeDisabled(); + + // Check the checkbox and validate the button is enabled + await iframe.locator("#checkbox0").check(); + await expect(workflowActionsButton).toBeEnabled(); + + // Click on the button and validate the dialog is present + await workflowActionsButton.click(); + await expect( + iframe.locator('.dijitDialog[role="dialog"]').first(), + ).toBeVisible(); }); - /** * Test to validate the search query is generating the correct results * @param page */ -test('Validate the search query', async ({page}) => { - const iframe = page.frameLocator(iFramesLocators.main_iframe); - await new ContentUtils(page).showQuery(iframe); - await expect(iframe.locator('#queryResults')).toBeVisible(); +test("Validate the search query", async ({ page }) => { + const iframe = page.frameLocator(iFramesLocators.main_iframe); + await new ContentUtils(page).showQuery(iframe); + await expect(iframe.locator("#queryResults")).toBeVisible(); }); /** * Test to validate the API button is working in the search query modal * @param page - */ -test('Validate the API link in search query modal is working', async ({page}) => { - const iframe = page.frameLocator(iFramesLocators.main_iframe); - await new ContentUtils(page).showQuery(iframe); - - // Wait for the new tab to open - const queryModal = page.waitForEvent('popup'); - await iframe.getByText('API', {exact: true}).click(); - const newPage = await queryModal; - - // Validate the new tab has opened - await newPage.waitForLoadState(); - expect(newPage.url()).toContain('about:blank'); - - //close the new tab - await newPage.close(); + */ +test("Validate the API link in search query modal is working", async ({ + page, +}) => { + const iframe = page.frameLocator(iFramesLocators.main_iframe); + await new ContentUtils(page).showQuery(iframe); + + // Wait for the new tab to open + const queryModal = page.waitForEvent("popup"); + await iframe.getByText("API", { exact: true }).click(); + const newPage = await queryModal; + + // Validate the new tab has opened + await newPage.waitForLoadState(); + expect(newPage.url()).toContain("about:blank"); + + //close the new tab + await newPage.close(); }); /** * Test to validate the clear button in the search filter * @param page - */ -test("Validate the clear button in the search filter", async ({page}) => { - const iframe = page.frameLocator(iFramesLocators.main_iframe); - - //expand the search filter - const advancedLinkLocator = iframe.getByRole('link', {name: 'Advanced'}); - await waitForVisibleAndCallback(advancedLinkLocator, () => advancedLinkLocator.click()); - - // Select the workflow in the search filter - await iframe.locator('#widget_scheme_id [data-dojo-attach-point="_buttonNode"]').click(); - await iframe.getByRole('option', {name: 'System Workflow'}).click(); - //TODO remove this - await page.waitForTimeout(1000); - - // Select the step in the search filter - const widgetStepIdLocator = iframe.locator('div[id=\'widget_step_id\'] div[data-dojo-attach-point=\'_buttonNode\']'); - await waitForVisibleAndCallback(widgetStepIdLocator, () => widgetStepIdLocator.click()); - const newOption = iframe.getByRole('option', {name: 'New'}); - await waitForVisibleAndCallback(newOption, () => newOption.click()); - - // Select the Show in the search filter - await iframe.locator('#widget_showingSelect [data-dojo-attach-point="_buttonNode"]').click(); - await iframe.getByRole('option', {name: 'Unpublished'}).click(); - - // Click on clear button - await iframe.getByLabel('Clear').click(); - - // Validate the search filter has been cleared - expect(await iframe.locator('input[name="scheme_id_select"]').getAttribute('value')).toBe('catchall'); - expect(await iframe.locator('input[name="step_id_select"]').getAttribute('value')).toBe('catchall'); - expect(await iframe.locator('#showingSelect').getAttribute('value')).toBe('All'); + */ +test("Validate the clear button in the search filter", async ({ page }) => { + const iframe = page.frameLocator(iFramesLocators.main_iframe); + + //expand the search filter + const advancedLinkLocator = iframe.getByRole("link", { name: "Advanced" }); + await waitForVisibleAndCallback(advancedLinkLocator, () => + advancedLinkLocator.click(), + ); + + // Select the workflow in the search filter + await iframe + .locator('#widget_scheme_id [data-dojo-attach-point="_buttonNode"]') + .click(); + await iframe.getByRole("option", { name: "System Workflow" }).click(); + //TODO remove this + await page.waitForTimeout(1000); + + // Select the step in the search filter + const widgetStepIdLocator = iframe.locator( + "div[id='widget_step_id'] div[data-dojo-attach-point='_buttonNode']", + ); + await waitForVisibleAndCallback(widgetStepIdLocator, () => + widgetStepIdLocator.click(), + ); + const newOption = iframe.getByRole("option", { name: "New" }); + await waitForVisibleAndCallback(newOption, () => newOption.click()); + + // Select the Show in the search filter + await iframe + .locator('#widget_showingSelect [data-dojo-attach-point="_buttonNode"]') + .click(); + await iframe.getByRole("option", { name: "Unpublished" }).click(); + + // Click on clear button + await iframe.getByLabel("Clear").click(); + + // Validate the search filter has been cleared + expect( + await iframe + .locator('input[name="scheme_id_select"]') + .getAttribute("value"), + ).toBe("catchall"); + expect( + await iframe.locator('input[name="step_id_select"]').getAttribute("value"), + ).toBe("catchall"); + expect(await iframe.locator("#showingSelect").getAttribute("value")).toBe( + "All", + ); }); /** * Test to validate the hide button in the search filter * @param page */ -test('Validate the hide button collapse the filter', async ({page}) => { - const iframe = page.frameLocator(iFramesLocators.main_iframe); - - await expect(iframe.getByRole('button', {name: 'Search'})).toBeVisible(); - let advancedLinkLocator = iframe.getByRole('link', {name: 'Advanced'}); - await waitForVisibleAndCallback(advancedLinkLocator, () => advancedLinkLocator.click()); - - await page.waitForTimeout(1000); - await expect(iframe.getByRole('link', {name: 'Advanced'})).not.toBeVisible(); - - // Click on the hide button - await iframe.getByRole('link', {name: 'Hide'}).click(); - await page.waitForTimeout(1000); - - // Validate the filter has been collapsed - await expect(iframe.getByRole('link', {name: 'Advanced'})).toBeVisible(); +test("Validate the hide button collapse the filter", async ({ page }) => { + const iframe = page.frameLocator(iFramesLocators.main_iframe); + + await expect(iframe.getByRole("button", { name: "Search" })).toBeVisible(); + let advancedLinkLocator = iframe.getByRole("link", { name: "Advanced" }); + await waitForVisibleAndCallback(advancedLinkLocator, () => + advancedLinkLocator.click(), + ); + + await page.waitForTimeout(1000); + await expect( + iframe.getByRole("link", { name: "Advanced" }), + ).not.toBeVisible(); + + // Click on the hide button + await iframe.getByRole("link", { name: "Hide" }).click(); + await page.waitForTimeout(1000); + + // Validate the filter has been collapsed + await expect(iframe.getByRole("link", { name: "Advanced" })).toBeVisible(); }); diff --git a/e2e/dotcms-e2e-node/frontend/tests/login/credentialsData.ts b/e2e/dotcms-e2e-node/frontend/tests/login/credentialsData.ts index 8f46ed4bc30a..444ffe912a36 100644 --- a/e2e/dotcms-e2e-node/frontend/tests/login/credentialsData.ts +++ b/e2e/dotcms-e2e-node/frontend/tests/login/credentialsData.ts @@ -2,35 +2,30 @@ * This is a valid user that can do everything */ export const admin1 = { - username: "admin@dotcms.com", - password: "admin" -} + username: "admin@dotcms.com", + password: "admin", +}; /** * This is a limited user that can only view the content */ export const limited1 = { - username: "chris@dotcms.com", - password: "chris" -} + username: "chris@dotcms.com", + password: "chris", +}; /** * Combination of a valid user and wrong password */ export const wrong1 = { - username: "admin@dotcms.com", - password: "password" -} + username: "admin@dotcms.com", + password: "password", +}; /** * Combination of a wrong user and valid password */ export const wrong2 = { - username: "chris2@dotcms.com", - password: "chris" -} - - - - - + username: "chris2@dotcms.com", + password: "chris", +}; diff --git a/e2e/dotcms-e2e-node/frontend/tests/login/login.spec.ts b/e2e/dotcms-e2e-node/frontend/tests/login/login.spec.ts index fc0d2b6c56e2..c6df4de8898d 100644 --- a/e2e/dotcms-e2e-node/frontend/tests/login/login.spec.ts +++ b/e2e/dotcms-e2e-node/frontend/tests/login/login.spec.ts @@ -1,47 +1,49 @@ -import {test, expect} from '@playwright/test'; -import {admin1, limited1, wrong1, wrong2} from './credentialsData'; - +import { test, expect } from "@playwright/test"; +import { admin1, limited1, wrong1, wrong2 } from "./credentialsData"; const validCredentials = [ - {username: admin1.username, password: admin1.password}, // admin user + { username: admin1.username, password: admin1.password }, // admin user ]; /** * Test to validate the login functionality with valid credentials */ -validCredentials.forEach(({username, password}) => { - test(`Login with Valid Credentials: ${username}`, async ({page}) => { - await page.goto('/dotAdmin'); - - await page.fill('input[id="inputtext"]', username); - await page.fill('input[id="password"]', password); - await page.getByTestId('submitButton').click(); - - // Assertion and further test steps - await expect(page.getByRole('link', {name: 'Getting Started'})).toBeVisible(); - }); +validCredentials.forEach(({ username, password }) => { + test(`Login with Valid Credentials: ${username}`, async ({ page }) => { + await page.goto("/dotAdmin"); + + await page.fill('input[id="inputtext"]', username); + await page.fill('input[id="password"]', password); + await page.getByTestId("submitButton").click(); + + // Assertion and further test steps + await expect( + page.getByRole("link", { name: "Getting Started" }), + ).toBeVisible(); + }); }); - const invalidCredentials = [ - {username: wrong1.username, password: wrong1.password}, // Valid username, invalid password - {username: wrong2.username, password: wrong2.password}, // Invalid username, valid password + { username: wrong1.username, password: wrong1.password }, // Valid username, invalid password + { username: wrong2.username, password: wrong2.password }, // Invalid username, valid password ]; /** * Test to validate the login functionality with invalid credentials */ -invalidCredentials.forEach(credentials => { - test(`Login with invalid Credentials: ${credentials.username}`, async ({page}) => { - const {username, password} = credentials; +invalidCredentials.forEach((credentials) => { + test(`Login with invalid Credentials: ${credentials.username}`, async ({ + page, + }) => { + const { username, password } = credentials; - await page.goto('/dotAdmin'); + await page.goto("/dotAdmin"); - await page.fill('input[id="inputtext"]', username); - await page.fill('input[id="password"]', password); - await page.getByTestId('submitButton').click(); + await page.fill('input[id="inputtext"]', username); + await page.fill('input[id="password"]', password); + await page.getByTestId("submitButton").click(); - // Assertion and further test steps - await expect(page.getByTestId('message')).toBeVisible({timeout: 30000}); - }); -}); \ No newline at end of file + // Assertion and further test steps + await expect(page.getByTestId("message")).toBeVisible({ timeout: 30000 }); + }); +}); diff --git a/e2e/dotcms-e2e-node/frontend/tests/login/translations.spec.ts b/e2e/dotcms-e2e-node/frontend/tests/login/translations.spec.ts index bdccbf35de41..76317ceb5b95 100644 --- a/e2e/dotcms-e2e-node/frontend/tests/login/translations.spec.ts +++ b/e2e/dotcms-e2e-node/frontend/tests/login/translations.spec.ts @@ -1,33 +1,37 @@ -import {expect, test} from '@playwright/test'; -import {assert} from 'console'; -import {waitForVisibleAndCallback} from "../../utils/dotCMSUtils"; +import { expect, test } from "@playwright/test"; +import { assert } from "console"; +import { waitForVisibleAndCallback } from "../../utils/dotCMSUtils"; const languages = [ - {language: 'español (España)', translation: '¡Bienvenido!'}, - {language: 'italiano (Italia)', translation: 'Benvenuto!'}, - {language: 'français (France)', translation: 'Bienvenue !'}, - {language: 'Deutsch (Deutschland)', translation: 'Willkommen!'}, - {language: '中文 (中国)', translation: '欢迎'}, - {language: 'Nederlands (Nederland)', translation: 'Welkom!'}, - {language: 'русский (Россия)', translation: 'Добро пожаловать!'}, - {language: 'suomi (Suomi)', translation: 'Tervetuloa!'} + { language: "español (España)", translation: "¡Bienvenido!" }, + { language: "italiano (Italia)", translation: "Benvenuto!" }, + { language: "français (France)", translation: "Bienvenue !" }, + { language: "Deutsch (Deutschland)", translation: "Willkommen!" }, + { language: "中文 (中国)", translation: "欢迎" }, + { language: "Nederlands (Nederland)", translation: "Welkom!" }, + { language: "русский (Россия)", translation: "Добро пожаловать!" }, + { language: "suomi (Suomi)", translation: "Tervetuloa!" }, ]; /** * Test to validate the translations of the login page */ -languages.forEach(list => { - test(`Validate Translation: ${list.language}`, async ({page}) => { - const {language, translation} = list; +languages.forEach((list) => { + test(`Validate Translation: ${list.language}`, async ({ page }) => { + const { language, translation } = list; - await page.goto('/dotAdmin'); - const dropdownTriggerLocator = page.getByLabel('dropdown trigger'); - await waitForVisibleAndCallback(dropdownTriggerLocator, () => dropdownTriggerLocator.click()); + await page.goto("/dotAdmin"); + const dropdownTriggerLocator = page.getByLabel("dropdown trigger"); + await waitForVisibleAndCallback(dropdownTriggerLocator, () => + dropdownTriggerLocator.click(), + ); - const pageByTextLocator = page.getByText(language); - await waitForVisibleAndCallback(pageByTextLocator, () => pageByTextLocator.click()); + const pageByTextLocator = page.getByText(language); + await waitForVisibleAndCallback(pageByTextLocator, () => + pageByTextLocator.click(), + ); - // Assertion of the translation - assert(await expect(page.getByTestId('header')).toContainText(translation)); - }); + // Assertion of the translation + assert(await expect(page.getByTestId("header")).toContainText(translation)); + }); }); diff --git a/e2e/dotcms-e2e-node/frontend/utils/contentUtils.ts b/e2e/dotcms-e2e-node/frontend/utils/contentUtils.ts index e686e73b4c44..c8d9c3e2caa5 100644 --- a/e2e/dotcms-e2e-node/frontend/utils/contentUtils.ts +++ b/e2e/dotcms-e2e-node/frontend/utils/contentUtils.ts @@ -1,356 +1,452 @@ -import {expect, FrameLocator, Locator, Page} from '@playwright/test'; -import {contentGeneric, iFramesLocators, fileAsset, pageAsset} from '../locators/globalLocators'; -import {waitForVisibleAndCallback} from './dotCMSUtils'; -import {contentProperties} from "../tests/contentSearch/contentData"; - +import { expect, FrameLocator, Locator, Page } from "@playwright/test"; +import { + contentGeneric, + iFramesLocators, + fileAsset, + pageAsset, +} from "../locators/globalLocators"; +import { waitForVisibleAndCallback } from "./dotCMSUtils"; +import { contentProperties } from "../tests/contentSearch/contentData"; export class ContentUtils { - page: Page; - - constructor(page: Page) { - this.page = page; + page: Page; + + constructor(page: Page) { + this.page = page; + } + + /** + * Fill the rich text form + * @param page + * @param title + * @param body + * @param action + */ + async fillRichTextForm( + page: Page, + title: string, + body: string, + action: string, + ) { + const dotIframe = page.frameLocator(iFramesLocators.dot_iframe); + + const headingLocator = page.getByRole("heading"); + await waitForVisibleAndCallback(headingLocator, () => + expect.soft(headingLocator).toContainText(contentGeneric.label), + ); + + //Fill title + await dotIframe.locator("#title").fill(title); + //Fill body + await dotIframe.locator("#block-editor-body div").nth(1).fill(body); + //Click on action + await dotIframe.getByText(action).first().click(); + } + + /** + * Fill the file asset form + * @param params + */ + async fillFileAssetForm(params: FileAssetFormParams) { + const { page, host, title, action, fromURL, newFileName, newFileText } = + params; + const dotIframe = page.frameLocator(iFramesLocators.dot_iframe); + + await waitForVisibleAndCallback(page.getByRole("heading"), () => + expect.soft(page.getByRole("heading")).toContainText(fileAsset.label), + ); + + await dotIframe.locator("#HostSelector-hostFolderSelect").fill(host); + + if (newFileName && newFileText) { + await dotIframe + .getByRole("button", { name: " Create New File" }) + .click(); + await dotIframe.getByTestId("editor-file-name").fill(newFileName); + await dotIframe + .getByLabel("Editor content;Press Alt+F1") + .fill(newFileText); + await dotIframe.getByRole("button", { name: "Save" }).click(); + } else if (fromURL) { + await dotIframe + .getByRole("button", { name: " Import from URL" }) + .click(); + await dotIframe.getByTestId("url-input").fill(fromURL); + await dotIframe.getByRole("button", { name: " Import" }).click(); + await waitForVisibleAndCallback( + dotIframe.getByRole("button", { name: " Remove" }), + async () => {}, + ); } - /** - * Fill the rich text form - * @param page - * @param title - * @param body - * @param action - */ - async fillRichTextForm(page: Page, title: string, body: string, action: string) { - const dotIframe = page.frameLocator(iFramesLocators.dot_iframe); - - const headingLocator = page.getByRole('heading'); - await waitForVisibleAndCallback(headingLocator, () => expect.soft(headingLocator).toContainText(contentGeneric.label)); - - //Fill title - await dotIframe.locator('#title').fill(title); - //Fill body - await dotIframe.locator('#block-editor-body div').nth(1).fill(body); - //Click on action - await dotIframe.getByText(action).first().click(); - } - - /** - * Fill the file asset form - * @param params - */ - async fillFileAssetForm(params: FileAssetFormParams) { - const { page, host, title, action, fromURL, newFileName, newFileText } = params; - const dotIframe = page.frameLocator(iFramesLocators.dot_iframe); - - await waitForVisibleAndCallback(page.getByRole('heading'), () => - expect.soft(page.getByRole('heading')).toContainText(fileAsset.label) - ); - - await dotIframe.locator('#HostSelector-hostFolderSelect').fill(host); - - if (newFileName && newFileText) { - await dotIframe.getByRole('button', { name: ' Create New File' }).click(); - await dotIframe.getByTestId('editor-file-name').fill(newFileName); - await dotIframe.getByLabel('Editor content;Press Alt+F1').fill(newFileText); - await dotIframe.getByRole('button', { name: 'Save' }).click(); - } else if (fromURL) { - await dotIframe.getByRole('button', { name: ' Import from URL' }).click(); - await dotIframe.getByTestId('url-input').fill(fromURL); - await dotIframe.getByRole('button', { name: ' Import' }).click(); - await waitForVisibleAndCallback(dotIframe.getByRole('button', { name: ' Remove' }), async () => {}); - } - - await waitForVisibleAndCallback(dotIframe.locator('#title'), () => - dotIframe.locator('#title').fill(title) - ); + await waitForVisibleAndCallback(dotIframe.locator("#title"), () => + dotIframe.locator("#title").fill(title), + ); - if (action) { - await dotIframe.getByText(action).first().click(); - } + if (action) { + await dotIframe.getByText(action).first().click(); } - - /** - * Validate the workflow execution and close the modal - * @param page - * @param message - */ - async workflowExecutionValidationAndClose(page: Page, message: string) { - const dotIframe = page.frameLocator(iFramesLocators.dot_iframe); - - const executionConfirmation = dotIframe.getByText(message); - await waitForVisibleAndCallback(executionConfirmation, () => expect(executionConfirmation).toBeVisible()); - await expect(executionConfirmation).toBeHidden(); - //Click on close - const closeBtnLocator = page.getByTestId('close-button').getByRole('button'); - await waitForVisibleAndCallback(closeBtnLocator, () => closeBtnLocator.click()); + } + + /** + * Validate the workflow execution and close the modal + * @param page + * @param message + */ + async workflowExecutionValidationAndClose(page: Page, message: string) { + const dotIframe = page.frameLocator(iFramesLocators.dot_iframe); + + const executionConfirmation = dotIframe.getByText(message); + await waitForVisibleAndCallback(executionConfirmation, () => + expect(executionConfirmation).toBeVisible(), + ); + await expect(executionConfirmation).toBeHidden(); + //Click on close + const closeBtnLocator = page + .getByTestId("close-button") + .getByRole("button"); + await waitForVisibleAndCallback(closeBtnLocator, () => + closeBtnLocator.click(), + ); + } + + /** + * Add new content action on the content portlet + * @param page + * @param typeLocator + * @param typeString + */ + async addNewContentAction( + page: Page, + typeLocator: string, + typeString: string, + ) { + const iframe = page.frameLocator(iFramesLocators.main_iframe); + + const structureINodeLocator = iframe.locator("#structure_inode"); + await waitForVisibleAndCallback(structureINodeLocator, () => + expect(structureINodeLocator).toBeVisible(), + ); + await this.selectTypeOnFilter(page, typeLocator); + + await waitForVisibleAndCallback( + iframe.locator("#dijit_form_DropDownButton_0"), + () => iframe.locator("#dijit_form_DropDownButton_0").click(), + ); + await waitForVisibleAndCallback( + iframe.getByLabel("actionPrimaryMenu"), + async () => {}, + ); + await iframe.getByLabel("▼").getByText("Add New Content").click(); + const headingLocator = page.getByRole("heading"); + await waitForVisibleAndCallback(headingLocator, () => + expect(headingLocator).toHaveText(typeString), + ); + } + + /** + * Select content type on filter on the content portlet + * @param page + * @param typeLocator + */ + async selectTypeOnFilter(page: Page, typeLocator: string) { + const iframe = page.frameLocator(iFramesLocators.main_iframe); + + const structureINodeDivLocator = iframe + .locator("#widget_structure_inode div") + .first(); + await waitForVisibleAndCallback(structureINodeDivLocator, () => + structureINodeDivLocator.click(), + ); + const typeLocatorByTextLocator = iframe.getByText(typeLocator); + await waitForVisibleAndCallback(typeLocatorByTextLocator, () => + typeLocatorByTextLocator.click(), + ); + } + + /** + * 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; + } } - /** - * Add new content action on the content portlet - * @param page - * @param typeLocator - * @param typeString - */ - async addNewContentAction(page: Page, typeLocator: string, typeString: string) { - const iframe = page.frameLocator(iFramesLocators.main_iframe); - - const structureINodeLocator = iframe.locator('#structure_inode'); - await waitForVisibleAndCallback(structureINodeLocator, () => expect(structureINodeLocator).toBeVisible()); - await this.selectTypeOnFilter(page, typeLocator); - - await waitForVisibleAndCallback(iframe.locator('#dijit_form_DropDownButton_0'), () => iframe.locator('#dijit_form_DropDownButton_0').click()); - await waitForVisibleAndCallback(iframe.getByLabel('actionPrimaryMenu'), async () => {}); - await iframe.getByLabel('▼').getByText('Add New Content').click(); - const headingLocator = page.getByRole('heading'); - await waitForVisibleAndCallback(headingLocator, () => expect(headingLocator).toHaveText(typeString)); - }; - - /** - * Select content type on filter on the content portlet - * @param page - * @param typeLocator - */ - async selectTypeOnFilter(page: Page, typeLocator: string) { - const iframe = page.frameLocator(iFramesLocators.main_iframe); - - const structureINodeDivLocator = iframe.locator('#widget_structure_inode div').first(); - await waitForVisibleAndCallback(structureINodeDivLocator, () => structureINodeDivLocator.click()); - const typeLocatorByTextLocator = iframe.getByText(typeLocator); - await waitForVisibleAndCallback(typeLocatorByTextLocator, () => typeLocatorByTextLocator.click()); + 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(); + } } - - /** - * 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(); + console.log(`The content with the title ${title} does not exist`); + return null; + } + + /** + * Edit content on the content portlet + * @param page + * @param title + * @param newTitle + * @param newBody + * @param action + */ + async editContent( + page: Page, + title: string, + newTitle: string, + newBody: string, + action: string, + ) { + const iframe = page.frameLocator(iFramesLocators.main_iframe); + const contentElement = await this.getContentElement(page, title); + if (contentElement) { + await contentElement.click(); + } else { + console.log("Content not found"); + return; } - - /** - * Validate if the content exists in the results table on the content portlet - * @param page - * @param text - */ - async validateContentExist(page: Page, text: string) { - const iframe = page.frameLocator(iFramesLocators.main_iframe); - - await waitForVisibleAndCallback(iframe.locator('#results_table tbody tr:nth-of-type(2)'), async () => { - }); + await this.fillRichTextForm(page, newTitle, newBody, action); + await this.workflowExecutionValidationAndClose(page, "Content saved"); + } + + /** + * Delete content on the content portlet + * @param page + * @param title + */ + async deleteContent(page: Page, title: string) { + const iframe = page.frameLocator(iFramesLocators.main_iframe); + + while ((await this.getContentState(page, title)) !== null) { + const contentState = await this.getContentState(page, title); + + if (contentState === "published") { + await this.performWorkflowAction( + page, + title, + contentProperties.unpublishWfAction, + ); + } else if (contentState === "draft") { + await this.performWorkflowAction( + page, + title, + contentProperties.archiveWfAction, + ); + await iframe.getByRole("link", { name: "Advanced" }).click(); + await iframe.locator("#widget_showingSelect div").first().click(); + const dropDownMenu = iframe.getByRole("option", { name: "Archived" }); + await waitForVisibleAndCallback(dropDownMenu, () => + dropDownMenu.click(), + ); await page.waitForTimeout(1000); + } else if (contentState === "archived") { + await this.performWorkflowAction( + page, + title, + contentProperties.deleteWfAction, + ); + return; + } - const cells = iframe.locator('#results_table tbody tr:nth-of-type(2) td'); - const cellCount = await cells.count(); - - for (let j = 0; j < cellCount; j++) { - const cell = cells.nth(j); - const cellText = await cell.textContent(); - - if (cellText && cellText.includes(text)) { - console.log(`The text "${text}" exists in the results table.`); - return true; - } - } - - 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(); - } - } - console.log(`The content with the title ${title} does not exist`); - return null; + await page.waitForLoadState(); } - - - /** - * Edit content on the content portlet - * @param page - * @param title - * @param newTitle - * @param newBody - * @param action - */ - async editContent(page: Page, title: string, newTitle: string, newBody: string, action: string) { - const iframe = page.frameLocator(iFramesLocators.main_iframe); - const contentElement = await this.getContentElement(page, title); - if (contentElement) { - await contentElement.click(); - } else { - console.log('Content not found'); - return; - } - await this.fillRichTextForm(page, newTitle, newBody, action); - await this.workflowExecutionValidationAndClose(page, 'Content saved'); + } + + /** + * 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", + }); } - - /** - * Delete content on the content portlet - * @param page - * @param title - */ - async deleteContent(page: Page, title: string) { - const iframe = page.frameLocator(iFramesLocators.main_iframe); - - while (await this.getContentState(page, title) !== null) { - const contentState = await this.getContentState(page, title); - - if (contentState === 'published') { - await this.performWorkflowAction(page, title, contentProperties.unpublishWfAction); - } else if (contentState === 'draft') { - await this.performWorkflowAction(page, title, contentProperties.archiveWfAction); - await iframe.getByRole('link', {name: 'Advanced'}).click(); - await iframe.locator('#widget_showingSelect div').first().click(); - const dropDownMenu = iframe.getByRole('option', {name: 'Archived'}); - await waitForVisibleAndCallback(dropDownMenu, () => dropDownMenu.click()); - await page.waitForTimeout(1000); - } else if (contentState === 'archived') { - await this.performWorkflowAction(page, title, contentProperties.deleteWfAction); - return; - } - - await page.waitForLoadState(); - } + 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"); + } } - /** - * 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()); + 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(); } - - /** - * 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; + if (template) { + await dotIframe.locator("#widget_templateSel div").first().click(); + await dotIframe.getByText(template).click(); } - - - /** - * Fill the pageAsset form - * @param params - */ - async fillPageAssetForm(params: PageAssetFormParams) { - const { page, title, action, url, host, template, friendlyName, showOnMenu, sortOrder, cacheTTL } = params; - const dotIframe = page.frameLocator(iFramesLocators.dot_iframe); - - await waitForVisibleAndCallback(page.getByRole('heading'), () => - expect.soft(page.getByRole('heading')).toContainText(pageAsset.label) - ); - await dotIframe.locator('#titleBox').fill(title); - - if (url) await dotIframe.locator('#url').fill(url); - if (host) { - await dotIframe.locator('#hostFolder_field div').nth(2).click(); - await dotIframe.getByRole('treeitem', { name: host }).click(); - } - if (template) { - await dotIframe.locator('#widget_templateSel div').first().click(); - await dotIframe.getByText(template).click(); - } - if (friendlyName) await dotIframe.locator('#friendlyName').fill(friendlyName); - if (showOnMenu) await dotIframe.getByLabel('Content', { exact: true }).getByLabel('').check(); - if (sortOrder) await dotIframe.locator('#sortOrder').fill(sortOrder); - if (cacheTTL) await dotIframe.locator('#cachettlbox').fill(cacheTTL.toString()); - if (action) await dotIframe.getByText(action).first().click(); - } - - + if (friendlyName) + await dotIframe.locator("#friendlyName").fill(friendlyName); + if (showOnMenu) + await dotIframe + .getByLabel("Content", { exact: true }) + .getByLabel("") + .check(); + if (sortOrder) await dotIframe.locator("#sortOrder").fill(sortOrder); + if (cacheTTL) + await dotIframe.locator("#cachettlbox").fill(cacheTTL.toString()); + if (action) await dotIframe.getByText(action).first().click(); + } } /** * Base form params */ interface BaseFormParams { - page: Page; - title: string; - action?: string; + page: Page; + title: string; + action?: string; } /** * Parameter to fill the file asset form params */ interface FileAssetFormParams extends BaseFormParams { - host: string; - fileName?: string; - fromURL?: string; - newFileName?: string; - newFileText?: string; + host: string; + fileName?: string; + fromURL?: string; + newFileName?: string; + newFileText?: 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 77f4e0035785..a9a196c11fbb 100644 --- a/e2e/dotcms-e2e-node/frontend/utils/dotCMSUtils.ts +++ b/e2e/dotcms-e2e-node/frontend/utils/dotCMSUtils.ts @@ -1,57 +1,74 @@ -import { Page, expect, Locator } from '@playwright/test'; -import { loginLocators } from '../locators/globalLocators'; +import { Page, expect, Locator } from "@playwright/test"; +import { loginLocators } from "../locators/globalLocators"; export class dotCMSUtils { - page: Page; + page: Page; - /** - * Login to dotCMS - * @param page - * @param username - * @param password - */ - async login(page: Page, username: string, password: string) { - await page.goto('/dotAdmin'); - await page.waitForLoadState() - const userNameInputLocator = page.locator(loginLocators.userNameInput); - await waitForVisibleAndCallback(userNameInputLocator, () => userNameInputLocator.fill(username)); - const passwordInputLocator = page.locator(loginLocators.passwordInput); - await waitForVisibleAndCallback(passwordInputLocator, () => passwordInputLocator.fill(password)); - const loginBtnLocator = page.getByTestId(loginLocators.loginBtn); - await waitForVisibleAndCallback(loginBtnLocator, () => loginBtnLocator.click()); - const gettingStartedLocator = page.getByRole('link', { name: 'Getting Started' }); - await waitForVisibleAndCallback(gettingStartedLocator, () => expect(gettingStartedLocator).toBeVisible()); - } + /** + * Login to dotCMS + * @param page + * @param username + * @param password + */ + async login(page: Page, username: string, password: string) { + await page.goto("/dotAdmin"); + await page.waitForLoadState(); + const userNameInputLocator = page.locator(loginLocators.userNameInput); + await waitForVisibleAndCallback(userNameInputLocator, () => + userNameInputLocator.fill(username), + ); + const passwordInputLocator = page.locator(loginLocators.passwordInput); + await waitForVisibleAndCallback(passwordInputLocator, () => + passwordInputLocator.fill(password), + ); + const loginBtnLocator = page.getByTestId(loginLocators.loginBtn); + await waitForVisibleAndCallback(loginBtnLocator, () => + loginBtnLocator.click(), + ); + const gettingStartedLocator = page.getByRole("link", { + name: "Getting Started", + }); + await waitForVisibleAndCallback(gettingStartedLocator, () => + expect(gettingStartedLocator).toBeVisible(), + ); + } - /** - * Navigate to the content portlet providing the menu, group and tool locators - * @param menu - * @param group - * @param tool - */ - async navigate(menu : Locator, group : Locator, tool : Locator) { - await menu.click(); - await group.click(); - await tool.click(); - } -}; + /** + * Navigate to the content portlet providing the menu, group and tool locators + * @param menu + * @param group + * @param tool + */ + async navigate(menu: Locator, group: Locator, tool: Locator) { + await menu.click(); + await group.click(); + await tool.click(); + } +} /** * Wait for the locator to be in the provided state * @param locator * @param state */ -export const waitFor = async (locator: Locator, state: "attached" | "detached" | "visible" | "hidden"): Promise => { - await locator.waitFor({state: state}); -} +export const waitFor = async ( + locator: Locator, + state: "attached" | "detached" | "visible" | "hidden", +): Promise => { + await locator.waitFor({ state: state }); +}; /** * Wait for the locator to be visible * @param locator */ -export const waitForAndCallback = async (locator: Locator, state: "attached" | "detached" | "visible" | "hidden", callback: () => Promise): Promise => { - await waitFor(locator, state); - await callback(); +export const waitForAndCallback = async ( + locator: Locator, + state: "attached" | "detached" | "visible" | "hidden", + callback: () => Promise, +): Promise => { + await waitFor(locator, state); + await callback(); }; /** @@ -59,6 +76,9 @@ export const waitForAndCallback = async (locator: Locator, state: "attached" | " * @param locator * @param callback */ -export const waitForVisibleAndCallback = async (locator: Locator, callback: () => Promise): Promise => { - await waitForAndCallback(locator, 'visible', callback); -}; \ No newline at end of file +export const waitForVisibleAndCallback = async ( + locator: Locator, + callback: () => Promise, +): Promise => { + await waitForAndCallback(locator, "visible", callback); +}; diff --git a/e2e/dotcms-e2e-node/frontend/yarn.lock b/e2e/dotcms-e2e-node/frontend/yarn.lock index b24babe5f978..48c37d48d78b 100644 --- a/e2e/dotcms-e2e-node/frontend/yarn.lock +++ b/e2e/dotcms-e2e-node/frontend/yarn.lock @@ -2,6 +2,115 @@ # yarn lockfile v1 +"@eslint-community/eslint-utils@^4.2.0", "@eslint-community/eslint-utils@^4.4.0": + version "4.4.1" + resolved "https://registry.npmjs.org/@eslint-community/eslint-utils/-/eslint-utils-4.4.1.tgz#d1145bf2c20132d6400495d6df4bf59362fd9d56" + integrity sha512-s3O3waFUrMV8P/XaF/+ZTp1X9XBZW1a4B97ZnjQF2KYWaFD2A8KyFBsrsfSjEmjn3RGWAIuvlneuZm3CUK3jbA== + dependencies: + eslint-visitor-keys "^3.4.3" + +"@eslint-community/regexpp@^4.10.0", "@eslint-community/regexpp@^4.12.1": + version "4.12.1" + resolved "https://registry.npmjs.org/@eslint-community/regexpp/-/regexpp-4.12.1.tgz#cfc6cffe39df390a3841cde2abccf92eaa7ae0e0" + integrity sha512-CCZCDJuduB9OUkFkY2IgppNZMi2lBQgD2qzwXkEia16cge2pijY/aXi96CJMquDMn3nJdlPV1A5KrJEXwfLNzQ== + +"@eslint/config-array@^0.19.0": + version "0.19.1" + resolved "https://registry.npmjs.org/@eslint/config-array/-/config-array-0.19.1.tgz#734aaea2c40be22bbb1f2a9dac687c57a6a4c984" + integrity sha512-fo6Mtm5mWyKjA/Chy1BYTdn5mGJoDNjC7C64ug20ADsRDGrA85bN3uK3MaKbeRkRuuIEAR5N33Jr1pbm411/PA== + dependencies: + "@eslint/object-schema" "^2.1.5" + debug "^4.3.1" + minimatch "^3.1.2" + +"@eslint/core@^0.9.0": + version "0.9.1" + resolved "https://registry.npmjs.org/@eslint/core/-/core-0.9.1.tgz#31763847308ef6b7084a4505573ac9402c51f9d1" + integrity sha512-GuUdqkyyzQI5RMIWkHhvTWLCyLo1jNK3vzkSyaExH5kHPDHcuL2VOpHjmMY+y3+NC69qAKToBqldTBgYeLSr9Q== + dependencies: + "@types/json-schema" "^7.0.15" + +"@eslint/eslintrc@^3.2.0": + version "3.2.0" + resolved "https://registry.npmjs.org/@eslint/eslintrc/-/eslintrc-3.2.0.tgz#57470ac4e2e283a6bf76044d63281196e370542c" + integrity sha512-grOjVNN8P3hjJn/eIETF1wwd12DdnwFDoyceUJLYYdkpbwq3nLi+4fqrTAONx7XDALqlL220wC/RHSC/QTI/0w== + dependencies: + ajv "^6.12.4" + debug "^4.3.2" + espree "^10.0.1" + globals "^14.0.0" + ignore "^5.2.0" + import-fresh "^3.2.1" + js-yaml "^4.1.0" + minimatch "^3.1.2" + strip-json-comments "^3.1.1" + +"@eslint/js@9.17.0", "@eslint/js@^9.17.0": + version "9.17.0" + resolved "https://registry.npmjs.org/@eslint/js/-/js-9.17.0.tgz#1523e586791f80376a6f8398a3964455ecc651ec" + integrity sha512-Sxc4hqcs1kTu0iID3kcZDW3JHq2a77HO9P8CP6YEA/FpH3Ll8UXE2r/86Rz9YJLKme39S9vU5OWNjC6Xl0Cr3w== + +"@eslint/object-schema@^2.1.5": + version "2.1.5" + resolved "https://registry.npmjs.org/@eslint/object-schema/-/object-schema-2.1.5.tgz#8670a8f6258a2be5b2c620ff314a1d984c23eb2e" + integrity sha512-o0bhxnL89h5Bae5T318nFoFzGy+YE5i/gGkoPAgkmTVdRKTiv3p8JHevPiPaMwoloKfEiiaHlawCqaZMqRm+XQ== + +"@eslint/plugin-kit@^0.2.3": + version "0.2.4" + resolved "https://registry.npmjs.org/@eslint/plugin-kit/-/plugin-kit-0.2.4.tgz#2b78e7bb3755784bb13faa8932a1d994d6537792" + integrity sha512-zSkKow6H5Kdm0ZUQUB2kV5JIXqoG0+uH5YADhaEHswm664N9Db8dXSi0nMJpacpMf+MyyglF1vnZohpEg5yUtg== + dependencies: + levn "^0.4.1" + +"@humanfs/core@^0.19.1": + version "0.19.1" + resolved "https://registry.npmjs.org/@humanfs/core/-/core-0.19.1.tgz#17c55ca7d426733fe3c561906b8173c336b40a77" + integrity sha512-5DyQ4+1JEUzejeK1JGICcideyfUbGixgS9jNgex5nqkW+cY7WZhxBigmieN5Qnw9ZosSNVC9KQKyb+GUaGyKUA== + +"@humanfs/node@^0.16.6": + version "0.16.6" + resolved "https://registry.npmjs.org/@humanfs/node/-/node-0.16.6.tgz#ee2a10eaabd1131987bf0488fd9b820174cd765e" + integrity sha512-YuI2ZHQL78Q5HbhDiBA1X4LmYdXCKCMQIfw0pw7piHJwyREFebJUvrQN4cMssyES6x+vfUbx1CIpaQUKYdQZOw== + dependencies: + "@humanfs/core" "^0.19.1" + "@humanwhocodes/retry" "^0.3.0" + +"@humanwhocodes/module-importer@^1.0.1": + version "1.0.1" + resolved "https://registry.npmjs.org/@humanwhocodes/module-importer/-/module-importer-1.0.1.tgz#af5b2691a22b44be847b0ca81641c5fb6ad0172c" + integrity sha512-bxveV4V8v5Yb4ncFTT3rPSgZBOpCkjfK0y4oVVVJwIuDVBRMDXrPyXRL988i5ap9m9bnyEEjWfm5WkBmtffLfA== + +"@humanwhocodes/retry@^0.3.0": + version "0.3.1" + resolved "https://registry.npmjs.org/@humanwhocodes/retry/-/retry-0.3.1.tgz#c72a5c76a9fbaf3488e231b13dc52c0da7bab42a" + integrity sha512-JBxkERygn7Bv/GbN5Rv8Ul6LVknS+5Bp6RgDC/O8gEBU/yeH5Ui5C/OlWrTb6qct7LjjfT6Re2NxB0ln0yYybA== + +"@humanwhocodes/retry@^0.4.1": + version "0.4.1" + resolved "https://registry.npmjs.org/@humanwhocodes/retry/-/retry-0.4.1.tgz#9a96ce501bc62df46c4031fbd970e3cc6b10f07b" + integrity sha512-c7hNEllBlenFTHBky65mhq8WD2kbN9Q6gk0bTk8lSBvc554jpXSkST1iePudpt7+A/AQvuHs9EMqjHDXMY1lrA== + +"@nodelib/fs.scandir@2.1.5": + version "2.1.5" + resolved "https://registry.npmjs.org/@nodelib/fs.scandir/-/fs.scandir-2.1.5.tgz#7619c2eb21b25483f6d167548b4cfd5a7488c3d5" + integrity sha512-vq24Bq3ym5HEQm2NKCr3yXDwjc7vTsEThRDnkp2DK9p1uqLR+DHurm/NOTo0KG7HYHU7eppKZj3MyqYuMBf62g== + dependencies: + "@nodelib/fs.stat" "2.0.5" + run-parallel "^1.1.9" + +"@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#5bd262af94e9d25bd1e71b05deed44876a222e8b" + integrity sha512-RkhPPp2zrqDAQA/2jNhnztcPAlv64XdhIp7a7454A5ovI7Bukxgt7MX7udwAu3zg1DcpPU0rz3VV1SeaqvY4+A== + +"@nodelib/fs.walk@^1.2.3": + version "1.2.8" + resolved "https://registry.npmjs.org/@nodelib/fs.walk/-/fs.walk-1.2.8.tgz#e95737e8bb6746ddedf69c556953494f196fe69a" + integrity sha512-oGB+UxlgWcgQkgwo8GcEGwemoTFt3FIO9ababBmaGwXIoBKZ+GTy0pP185beGg7Llih/NSHSV2XAs1lnznocSg== + dependencies: + "@nodelib/fs.scandir" "2.1.5" + fastq "^1.6.0" + "@playwright/test@^1.48.2": version "1.48.2" resolved "https://registry.npmjs.org/@playwright/test/-/test-1.48.2.tgz" @@ -9,6 +118,16 @@ dependencies: playwright "1.48.2" +"@types/estree@^1.0.6": + version "1.0.6" + resolved "https://registry.npmjs.org/@types/estree/-/estree-1.0.6.tgz#628effeeae2064a1b4e79f78e81d87b7e5fc7b50" + integrity sha512-AYnb1nQyY49te+VRAVgmzfcgjYS91mY5P0TKUDCLEM+gNnA+3T6rWITXRLYCpahpqSQbN5cE+gHpnPyXjHWxcw== + +"@types/json-schema@^7.0.15": + version "7.0.15" + resolved "https://registry.npmjs.org/@types/json-schema/-/json-schema-7.0.15.tgz#596a1747233694d50f6ad8a7869fcb6f56cf5841" + integrity sha512-5+fP8P8MFNC+AyZCDxrB2pkZFPGzqQWUzpSeuuVLvm8VMcorNYavBqoFcxK8bQz4Qsbn4oUEEem4wDLfcysGHA== + "@types/node@^22.5.4": version "22.5.4" resolved "https://registry.npmjs.org/@types/node/-/node-22.5.4.tgz" @@ -16,6 +135,97 @@ dependencies: undici-types "~6.19.2" +"@typescript-eslint/eslint-plugin@8.19.0", "@typescript-eslint/eslint-plugin@^8.19.0": + version "8.19.0" + resolved "https://registry.npmjs.org/@typescript-eslint/eslint-plugin/-/eslint-plugin-8.19.0.tgz#2b1e1b791e21d5fc27ddc93884db066444f597b5" + integrity sha512-NggSaEZCdSrFddbctrVjkVZvFC6KGfKfNK0CU7mNK/iKHGKbzT4Wmgm08dKpcZECBu9f5FypndoMyRHkdqfT1Q== + dependencies: + "@eslint-community/regexpp" "^4.10.0" + "@typescript-eslint/scope-manager" "8.19.0" + "@typescript-eslint/type-utils" "8.19.0" + "@typescript-eslint/utils" "8.19.0" + "@typescript-eslint/visitor-keys" "8.19.0" + graphemer "^1.4.0" + ignore "^5.3.1" + natural-compare "^1.4.0" + ts-api-utils "^1.3.0" + +"@typescript-eslint/parser@8.19.0": + version "8.19.0" + resolved "https://registry.npmjs.org/@typescript-eslint/parser/-/parser-8.19.0.tgz#f1512e6e5c491b03aabb2718b95becde22b15292" + integrity sha512-6M8taKyOETY1TKHp0x8ndycipTVgmp4xtg5QpEZzXxDhNvvHOJi5rLRkLr8SK3jTgD5l4fTlvBiRdfsuWydxBw== + dependencies: + "@typescript-eslint/scope-manager" "8.19.0" + "@typescript-eslint/types" "8.19.0" + "@typescript-eslint/typescript-estree" "8.19.0" + "@typescript-eslint/visitor-keys" "8.19.0" + debug "^4.3.4" + +"@typescript-eslint/scope-manager@8.19.0": + version "8.19.0" + resolved "https://registry.npmjs.org/@typescript-eslint/scope-manager/-/scope-manager-8.19.0.tgz#28fa413a334f70e8b506a968531e0a7c9c3076dc" + integrity sha512-hkoJiKQS3GQ13TSMEiuNmSCvhz7ujyqD1x3ShbaETATHrck+9RaDdUbt+osXaUuns9OFwrDTTrjtwsU8gJyyRA== + dependencies: + "@typescript-eslint/types" "8.19.0" + "@typescript-eslint/visitor-keys" "8.19.0" + +"@typescript-eslint/type-utils@8.19.0": + version "8.19.0" + resolved "https://registry.npmjs.org/@typescript-eslint/type-utils/-/type-utils-8.19.0.tgz#41abd7d2e4cf93b6854b1fe6cbf416fab5abf89f" + integrity sha512-TZs0I0OSbd5Aza4qAMpp1cdCYVnER94IziudE3JU328YUHgWu9gwiwhag+fuLeJ2LkWLXI+F/182TbG+JaBdTg== + dependencies: + "@typescript-eslint/typescript-estree" "8.19.0" + "@typescript-eslint/utils" "8.19.0" + debug "^4.3.4" + ts-api-utils "^1.3.0" + +"@typescript-eslint/types@8.19.0": + version "8.19.0" + resolved "https://registry.npmjs.org/@typescript-eslint/types/-/types-8.19.0.tgz#a190a25c5484a42b81eaad06989579fdeb478cbb" + integrity sha512-8XQ4Ss7G9WX8oaYvD4OOLCjIQYgRQxO+qCiR2V2s2GxI9AUpo7riNwo6jDhKtTcaJjT8PY54j2Yb33kWtSJsmA== + +"@typescript-eslint/typescript-estree@8.19.0": + version "8.19.0" + resolved "https://registry.npmjs.org/@typescript-eslint/typescript-estree/-/typescript-estree-8.19.0.tgz#6b4f48f98ffad6597379951b115710f4d68c9ccb" + integrity sha512-WW9PpDaLIFW9LCbucMSdYUuGeFUz1OkWYS/5fwZwTA+l2RwlWFdJvReQqMUMBw4yJWJOfqd7An9uwut2Oj8sLw== + dependencies: + "@typescript-eslint/types" "8.19.0" + "@typescript-eslint/visitor-keys" "8.19.0" + debug "^4.3.4" + fast-glob "^3.3.2" + is-glob "^4.0.3" + minimatch "^9.0.4" + semver "^7.6.0" + ts-api-utils "^1.3.0" + +"@typescript-eslint/utils@8.19.0": + version "8.19.0" + resolved "https://registry.npmjs.org/@typescript-eslint/utils/-/utils-8.19.0.tgz#33824310e1fccc17f27fbd1030fd8bbd9a674684" + integrity sha512-PTBG+0oEMPH9jCZlfg07LCB2nYI0I317yyvXGfxnvGvw4SHIOuRnQ3kadyyXY6tGdChusIHIbM5zfIbp4M6tCg== + dependencies: + "@eslint-community/eslint-utils" "^4.4.0" + "@typescript-eslint/scope-manager" "8.19.0" + "@typescript-eslint/types" "8.19.0" + "@typescript-eslint/typescript-estree" "8.19.0" + +"@typescript-eslint/visitor-keys@8.19.0": + version "8.19.0" + resolved "https://registry.npmjs.org/@typescript-eslint/visitor-keys/-/visitor-keys-8.19.0.tgz#dc313f735e64c4979c9073f51ffcefb6d9be5c77" + integrity sha512-mCFtBbFBJDCNCWUl5y6sZSCHXw1DEFEk3c/M3nRK2a4XUB8StGFtmcEMizdjKuBzB6e/smJAAWYug3VrdLMr1w== + dependencies: + "@typescript-eslint/types" "8.19.0" + eslint-visitor-keys "^4.2.0" + +acorn-jsx@^5.3.2: + version "5.3.2" + resolved "https://registry.npmjs.org/acorn-jsx/-/acorn-jsx-5.3.2.tgz#7ed5bb55908b3b2f1bc55c6af1653bada7f07937" + integrity sha512-rq9s+JNhf0IChjtDXxllJ7g41oZk5SlXtp0LHwyA5cejwn7vKmKp4pPri6YEePv2PU65sAsegbXtIinmDFDXgQ== + +acorn@^8.14.0: + version "8.14.0" + resolved "https://registry.npmjs.org/acorn/-/acorn-8.14.0.tgz#063e2c70cac5fb4f6467f0b11152e04c682795b0" + integrity sha512-cl669nCJTZBsL97OF4kUQm5g5hC2uihk0NxY3WENAC0TYdILVkAyHymAntgxGkl7K+t0cXIrH5siy5S4XkFycA== + agent-base@^7.0.2, agent-base@^7.1.0: version "7.1.1" resolved "https://registry.npmjs.org/agent-base/-/agent-base-7.1.1.tgz" @@ -23,11 +233,85 @@ agent-base@^7.0.2, agent-base@^7.1.0: dependencies: debug "^4.3.4" +ajv@^6.12.4: + version "6.12.6" + resolved "https://registry.npmjs.org/ajv/-/ajv-6.12.6.tgz#baf5a62e802b07d977034586f8c3baf5adf26df4" + integrity sha512-j3fVLgvTo527anyYyJOGTYJbG+vnnQYvE0m5mmkc1TK+nxAppkCLMIL0aZ4dblVCNoGShhm+kzE4ZUykBoMg4g== + dependencies: + fast-deep-equal "^3.1.1" + fast-json-stable-stringify "^2.0.0" + json-schema-traverse "^0.4.1" + uri-js "^4.2.2" + +ansi-styles@^4.1.0: + version "4.3.0" + resolved "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz#edd803628ae71c04c85ae7a0906edad34b648937" + integrity sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg== + dependencies: + color-convert "^2.0.1" + +argparse@^2.0.1: + version "2.0.1" + resolved "https://registry.npmjs.org/argparse/-/argparse-2.0.1.tgz#246f50f3ca78a3240f6c997e8a9bd1eac49e4b38" + integrity sha512-8+9WqebbFzpX9OR+Wa6O29asIogeRMzcGtAINdpMHHyAg10f05aSFVBbcEqGf/PXw1EjAZ+q2/bEBg3DvurK3Q== + asynckit@^0.4.0: version "0.4.0" resolved "https://registry.npmjs.org/asynckit/-/asynckit-0.4.0.tgz" integrity sha512-Oei9OH4tRh0YqU3GxhX79dM/mwVgvbZJaSNaRk+bshkj0S5cfHcgYakreBjrHwatXKbz+IoIdYLxrKim2MjW0Q== +balanced-match@^1.0.0: + version "1.0.2" + resolved "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.2.tgz#e83e3a7e3f300b34cb9d87f615fa0cbf357690ee" + integrity sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw== + +brace-expansion@^1.1.7: + version "1.1.11" + resolved "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.11.tgz#3c7fcbf529d87226f3d2f52b966ff5271eb441dd" + integrity sha512-iCuPHDFgrHX7H2vEI/5xpz07zSHB00TpugqhmYtVmMO6518mCuRMoOYFldEBl0g187ufozdaHgWKcYFb61qGiA== + dependencies: + balanced-match "^1.0.0" + concat-map "0.0.1" + +brace-expansion@^2.0.1: + version "2.0.1" + resolved "https://registry.npmjs.org/brace-expansion/-/brace-expansion-2.0.1.tgz#1edc459e0f0c548486ecf9fc99f2221364b9a0ae" + integrity sha512-XnAIvQ8eM+kC6aULx6wuQiwVsnzsi9d3WxzV3FpWTGA19F621kwdbsAcFKXgKUHZWsy+mY6iL1sHTxWEFCytDA== + dependencies: + balanced-match "^1.0.0" + +braces@^3.0.3: + version "3.0.3" + resolved "https://registry.npmjs.org/braces/-/braces-3.0.3.tgz#490332f40919452272d55a8480adc0c441358789" + integrity sha512-yQbXgO/OSZVD2IsiLlro+7Hf6Q18EJrKSEsdoMzKePKXct3gvD8oLcOQdIzGupr5Fj+EDe8gO/lxc1BzfMpxvA== + dependencies: + fill-range "^7.1.1" + +callsites@^3.0.0: + version "3.1.0" + resolved "https://registry.npmjs.org/callsites/-/callsites-3.1.0.tgz#b3630abd8943432f54b3f0519238e33cd7df2f73" + integrity sha512-P8BjAsXvZS+VIDUI11hHCQEv74YT67YUi5JJFNWIqL235sBmjX4+qx9Muvls5ivyNENctx46xQLQ3aTuE7ssaQ== + +chalk@^4.0.0: + version "4.1.2" + resolved "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz#aac4e2b7734a740867aeb16bf02aad556a1e7a01" + integrity sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA== + dependencies: + ansi-styles "^4.1.0" + supports-color "^7.1.0" + +color-convert@^2.0.1: + version "2.0.1" + resolved "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz#72d3a68d598c9bdb3af2ad1e84f21d896abd4de3" + integrity sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ== + dependencies: + color-name "~1.1.4" + +color-name@~1.1.4: + version "1.1.4" + resolved "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz#c2a09a87acbde69543de6f63fa3995c826c536a2" + integrity sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA== + combined-stream@^1.0.8: version "1.0.8" resolved "https://registry.npmjs.org/combined-stream/-/combined-stream-1.0.8.tgz" @@ -35,6 +319,20 @@ combined-stream@^1.0.8: dependencies: delayed-stream "~1.0.0" +concat-map@0.0.1: + version "0.0.1" + resolved "https://registry.npmjs.org/concat-map/-/concat-map-0.0.1.tgz#d8a96bd77fd68df7793a73036a3ba0d5405d477b" + integrity sha512-/Srv4dswyQNBfohGpz9o6Yb3Gz3SrUDqBH5rTuhGR7ahtlbYKnVxw2bCFMRljaA7EXHaXZ8wsHdodFvbkhKmqg== + +cross-spawn@^7.0.6: + version "7.0.6" + resolved "https://registry.npmjs.org/cross-spawn/-/cross-spawn-7.0.6.tgz#8a58fe78f00dcd70c370451759dfbfaf03e8ee9f" + integrity sha512-uV2QOWP2nWzsy2aMp8aRibhi9dlzF5Hgh5SHaB9OiTGEyDTiJJyx0uy51QXdyWbtAHNua4XJzUKca3OzKUd3vA== + dependencies: + path-key "^3.1.0" + shebang-command "^2.0.0" + which "^2.0.1" + cssstyle@^4.1.0: version "4.1.0" resolved "https://registry.npmjs.org/cssstyle/-/cssstyle-4.1.0.tgz" @@ -50,18 +348,30 @@ data-urls@^5.0.0: whatwg-mimetype "^4.0.0" whatwg-url "^14.0.0" -debug@^4.3.4, debug@4: +debug@4, debug@^4.3.4: version "4.3.7" resolved "https://registry.npmjs.org/debug/-/debug-4.3.7.tgz" integrity sha512-Er2nc/H7RrMXZBFCEim6TCmMk02Z8vLC2Rbi1KEBggpo0fS6l0S1nnapwmIi3yW/+GOJap1Krg4w0Hg80oCqgQ== dependencies: ms "^2.1.3" +debug@^4.3.1, debug@^4.3.2: + version "4.4.0" + resolved "https://registry.npmjs.org/debug/-/debug-4.4.0.tgz#2b3f2aea2ffeb776477460267377dc8710faba8a" + integrity sha512-6WTZ/IxCY/T6BALoZHaE4ctp9xm+Z5kY/pzYaCHRFeyVhojxlrm+46y68HA6hr0TcwEssoxNiDEUJQjfPZ/RYA== + dependencies: + ms "^2.1.3" + decimal.js@^10.4.3: version "10.4.3" resolved "https://registry.npmjs.org/decimal.js/-/decimal.js-10.4.3.tgz" integrity sha512-VBBaLc1MgL5XpzgIP7ny5Z6Nx3UrRkIViUkPUdtl9aya5amy3De1gsUUSB1g3+3sExYNjCAsAznmukyxCb1GRA== +deep-is@^0.1.3: + version "0.1.4" + resolved "https://registry.npmjs.org/deep-is/-/deep-is-0.1.4.tgz#a6f2dce612fadd2ef1f519b73551f17e85199831" + integrity sha512-oIPzksmTg4/MriiaYGO+okXDT7ztn/w3Eptv/+gSIdMdKsJo0u4CfYNFJPy+4SKMuCqGw2wxnA+URMg3t8a/bQ== + delayed-stream@~1.0.0: version "1.0.0" resolved "https://registry.npmjs.org/delayed-stream/-/delayed-stream-1.0.0.tgz" @@ -77,6 +387,182 @@ entities@^4.4.0: resolved "https://registry.npmjs.org/entities/-/entities-4.5.0.tgz" integrity sha512-V0hjH4dGPh9Ao5p0MoRY6BVqtwCjhz6vI5LT8AJ55H+4g9/4vbHx1I54fS0XuclLhDHArPQCiMjDxjaL8fPxhw== +escape-string-regexp@^4.0.0: + version "4.0.0" + resolved "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-4.0.0.tgz#14ba83a5d373e3d311e5afca29cf5bfad965bf34" + integrity sha512-TtpcNJ3XAzx3Gq8sWRzJaVajRs0uVxA2YAkdb1jm2YkPz4G6egUFAyA3n5vtEIZefPk5Wa4UXbKuS5fKkJWdgA== + +eslint-config-prettier@^9.1.0: + version "9.1.0" + resolved "https://registry.npmjs.org/eslint-config-prettier/-/eslint-config-prettier-9.1.0.tgz#31af3d94578645966c082fcb71a5846d3c94867f" + integrity sha512-NSWl5BFQWEPi1j4TjVNItzYV7dZXZ+wP6I6ZhrBGpChQhZRUaElihE9uRRkcbRnNb76UMKDF3r+WTmNcGPKsqw== + +eslint-plugin-playwright@^2.1.0: + version "2.1.0" + resolved "https://registry.npmjs.org/eslint-plugin-playwright/-/eslint-plugin-playwright-2.1.0.tgz#9d84b5d839226e1f9ac926663baee29d103dcc99" + integrity sha512-wMbHOehofSB1cBdzz2CLaCYaKNLeTQ0YnOW+7AHa281TJqlpEJUBgTHbRUYOUxiXphfWwOyTPvgr6vvEmArbSA== + dependencies: + globals "^13.23.0" + +eslint-scope@^8.2.0: + version "8.2.0" + resolved "https://registry.npmjs.org/eslint-scope/-/eslint-scope-8.2.0.tgz#377aa6f1cb5dc7592cfd0b7f892fd0cf352ce442" + integrity sha512-PHlWUfG6lvPc3yvP5A4PNyBL1W8fkDUccmI21JUu/+GKZBoH/W5u6usENXUrWFRsyoW5ACUjFGgAFQp5gUlb/A== + dependencies: + esrecurse "^4.3.0" + estraverse "^5.2.0" + +eslint-visitor-keys@^3.4.3: + version "3.4.3" + resolved "https://registry.npmjs.org/eslint-visitor-keys/-/eslint-visitor-keys-3.4.3.tgz#0cd72fe8550e3c2eae156a96a4dddcd1c8ac5800" + integrity sha512-wpc+LXeiyiisxPlEkUzU6svyS1frIO3Mgxj1fdy7Pm8Ygzguax2N3Fa/D/ag1WqbOprdI+uY6wMUl8/a2G+iag== + +eslint-visitor-keys@^4.2.0: + version "4.2.0" + resolved "https://registry.npmjs.org/eslint-visitor-keys/-/eslint-visitor-keys-4.2.0.tgz#687bacb2af884fcdda8a6e7d65c606f46a14cd45" + integrity sha512-UyLnSehNt62FFhSwjZlHmeokpRK59rcz29j+F1/aDgbkbRTk7wIc9XzdoasMUbRNKDM0qQt/+BJ4BrpFeABemw== + +eslint@^9.17.0: + version "9.17.0" + resolved "https://registry.npmjs.org/eslint/-/eslint-9.17.0.tgz#faa1facb5dd042172fdc520106984b5c2421bb0c" + integrity sha512-evtlNcpJg+cZLcnVKwsai8fExnqjGPicK7gnUtlNuzu+Fv9bI0aLpND5T44VLQtoMEnI57LoXO9XAkIXwohKrA== + dependencies: + "@eslint-community/eslint-utils" "^4.2.0" + "@eslint-community/regexpp" "^4.12.1" + "@eslint/config-array" "^0.19.0" + "@eslint/core" "^0.9.0" + "@eslint/eslintrc" "^3.2.0" + "@eslint/js" "9.17.0" + "@eslint/plugin-kit" "^0.2.3" + "@humanfs/node" "^0.16.6" + "@humanwhocodes/module-importer" "^1.0.1" + "@humanwhocodes/retry" "^0.4.1" + "@types/estree" "^1.0.6" + "@types/json-schema" "^7.0.15" + ajv "^6.12.4" + chalk "^4.0.0" + cross-spawn "^7.0.6" + debug "^4.3.2" + escape-string-regexp "^4.0.0" + eslint-scope "^8.2.0" + eslint-visitor-keys "^4.2.0" + espree "^10.3.0" + esquery "^1.5.0" + esutils "^2.0.2" + fast-deep-equal "^3.1.3" + file-entry-cache "^8.0.0" + find-up "^5.0.0" + glob-parent "^6.0.2" + ignore "^5.2.0" + imurmurhash "^0.1.4" + is-glob "^4.0.0" + json-stable-stringify-without-jsonify "^1.0.1" + lodash.merge "^4.6.2" + minimatch "^3.1.2" + natural-compare "^1.4.0" + optionator "^0.9.3" + +espree@^10.0.1, espree@^10.3.0: + version "10.3.0" + resolved "https://registry.npmjs.org/espree/-/espree-10.3.0.tgz#29267cf5b0cb98735b65e64ba07e0ed49d1eed8a" + integrity sha512-0QYC8b24HWY8zjRnDTL6RiHfDbAWn63qb4LMj1Z4b076A4une81+z03Kg7l7mn/48PUTqoLptSXez8oknU8Clg== + dependencies: + acorn "^8.14.0" + acorn-jsx "^5.3.2" + eslint-visitor-keys "^4.2.0" + +esquery@^1.5.0: + version "1.6.0" + resolved "https://registry.npmjs.org/esquery/-/esquery-1.6.0.tgz#91419234f804d852a82dceec3e16cdc22cf9dae7" + integrity sha512-ca9pw9fomFcKPvFLXhBKUK90ZvGibiGOvRJNbjljY7s7uq/5YO4BOzcYtJqExdx99rF6aAcnRxHmcUHcz6sQsg== + dependencies: + estraverse "^5.1.0" + +esrecurse@^4.3.0: + version "4.3.0" + resolved "https://registry.npmjs.org/esrecurse/-/esrecurse-4.3.0.tgz#7ad7964d679abb28bee72cec63758b1c5d2c9921" + integrity sha512-KmfKL3b6G+RXvP8N1vr3Tq1kL/oCFgn2NYXEtqP8/L3pKapUA4G8cFVaoF3SU323CD4XypR/ffioHmkti6/Tag== + dependencies: + estraverse "^5.2.0" + +estraverse@^5.1.0, estraverse@^5.2.0: + version "5.3.0" + resolved "https://registry.npmjs.org/estraverse/-/estraverse-5.3.0.tgz#2eea5290702f26ab8fe5370370ff86c965d21123" + integrity sha512-MMdARuVEQziNTeJD8DgMqmhwR11BRQ/cBP+pLtYdSTnf3MIO8fFeiINEbX36ZdNlfU/7A9f3gUw49B3oQsvwBA== + +esutils@^2.0.2: + version "2.0.3" + resolved "https://registry.npmjs.org/esutils/-/esutils-2.0.3.tgz#74d2eb4de0b8da1293711910d50775b9b710ef64" + integrity sha512-kVscqXk4OCp68SZ0dkgEKVi6/8ij300KBWTJq32P/dYeWTSwK41WyTxalN1eRmA5Z9UU/LX9D7FWSmV9SAYx6g== + +fast-deep-equal@^3.1.1, fast-deep-equal@^3.1.3: + version "3.1.3" + resolved "https://registry.npmjs.org/fast-deep-equal/-/fast-deep-equal-3.1.3.tgz#3a7d56b559d6cbc3eb512325244e619a65c6c525" + integrity sha512-f3qQ9oQy9j2AhBe/H9VC91wLmKBCCU/gDOnKNAYG5hswO7BLKj09Hc5HYNz9cGI++xlpDCIgDaitVs03ATR84Q== + +fast-glob@^3.3.2: + version "3.3.2" + resolved "https://registry.npmjs.org/fast-glob/-/fast-glob-3.3.2.tgz#a904501e57cfdd2ffcded45e99a54fef55e46129" + integrity sha512-oX2ruAFQwf/Orj8m737Y5adxDQO0LAB7/S5MnxCdTNDd4p6BsyIVsv9JQsATbTSq8KHRpLwIHbVlUNatxd+1Ow== + dependencies: + "@nodelib/fs.stat" "^2.0.2" + "@nodelib/fs.walk" "^1.2.3" + glob-parent "^5.1.2" + merge2 "^1.3.0" + micromatch "^4.0.4" + +fast-json-stable-stringify@^2.0.0: + version "2.1.0" + resolved "https://registry.npmjs.org/fast-json-stable-stringify/-/fast-json-stable-stringify-2.1.0.tgz#874bf69c6f404c2b5d99c481341399fd55892633" + integrity sha512-lhd/wF+Lk98HZoTCtlVraHtfh5XYijIjalXck7saUtuanSDyLMxnHhSXEDJqHxD7msR8D0uCmqlkwjCV8xvwHw== + +fast-levenshtein@^2.0.6: + version "2.0.6" + resolved "https://registry.npmjs.org/fast-levenshtein/-/fast-levenshtein-2.0.6.tgz#3d8a5c66883a16a30ca8643e851f19baa7797917" + integrity sha512-DCXu6Ifhqcks7TZKY3Hxp3y6qphY5SJZmrWMDrKcERSOXWQdMhU9Ig/PYrzyw/ul9jOIyh0N4M0tbC5hodg8dw== + +fastq@^1.6.0: + version "1.18.0" + resolved "https://registry.npmjs.org/fastq/-/fastq-1.18.0.tgz#d631d7e25faffea81887fe5ea8c9010e1b36fee0" + integrity sha512-QKHXPW0hD8g4UET03SdOdunzSouc9N4AuHdsX8XNcTsuz+yYFILVNIX4l9yHABMhiEI9Db0JTTIpu0wB+Y1QQw== + dependencies: + reusify "^1.0.4" + +file-entry-cache@^8.0.0: + version "8.0.0" + resolved "https://registry.npmjs.org/file-entry-cache/-/file-entry-cache-8.0.0.tgz#7787bddcf1131bffb92636c69457bbc0edd6d81f" + integrity sha512-XXTUwCvisa5oacNGRP9SfNtYBNAMi+RPwBFmblZEF7N7swHYQS6/Zfk7SRwx4D5j3CH211YNRco1DEMNVfZCnQ== + dependencies: + flat-cache "^4.0.0" + +fill-range@^7.1.1: + version "7.1.1" + resolved "https://registry.npmjs.org/fill-range/-/fill-range-7.1.1.tgz#44265d3cac07e3ea7dc247516380643754a05292" + integrity sha512-YsGpe3WHLK8ZYi4tWDg2Jy3ebRz2rXowDxnld4bkQB00cc/1Zw9AWnC0i9ztDJitivtQvaI9KaLyKrc+hBW0yg== + dependencies: + to-regex-range "^5.0.1" + +find-up@^5.0.0: + version "5.0.0" + resolved "https://registry.npmjs.org/find-up/-/find-up-5.0.0.tgz#4c92819ecb7083561e4f4a240a86be5198f536fc" + integrity sha512-78/PXT1wlLLDgTzDs7sjq9hzz0vXD+zn+7wypEe4fXQxCmdmqfGsEPQxmiCSQI3ajFV91bVSsvNtrJRiW6nGng== + dependencies: + locate-path "^6.0.0" + path-exists "^4.0.0" + +flat-cache@^4.0.0: + version "4.0.1" + resolved "https://registry.npmjs.org/flat-cache/-/flat-cache-4.0.1.tgz#0ece39fcb14ee012f4b0410bd33dd9c1f011127c" + integrity sha512-f7ccFPK3SXFHpx15UIGyRJ/FJQctuKZ0zVuN3frBo4HnK3cay9VEW0R6yPYFHC0AgqhukPzKjq22t5DmAyqGyw== + dependencies: + flatted "^3.2.9" + keyv "^4.5.4" + +flatted@^3.2.9: + version "3.3.2" + resolved "https://registry.npmjs.org/flatted/-/flatted-3.3.2.tgz#adba1448a9841bec72b42c532ea23dbbedef1a27" + integrity sha512-AiwGJM8YcNOaobumgtng+6NHuOqC3A7MixFeDafM3X9cIUM+xUXoS5Vfgf+OihAYe20fxqNM9yPBXJzRtZ/4eA== + form-data@^4.0.0: version "4.0.0" resolved "https://registry.npmjs.org/form-data/-/form-data-4.0.0.tgz" @@ -91,6 +577,42 @@ fsevents@2.3.2: resolved "https://registry.npmjs.org/fsevents/-/fsevents-2.3.2.tgz" integrity sha512-xiqMQR4xAeHTuB9uWm+fFRcIOgKBMiOBP+eXiyT7jsgVCq1bkVygt00oASowB7EdtpOHaaPgKt812P9ab+DDKA== +glob-parent@^5.1.2: + version "5.1.2" + resolved "https://registry.npmjs.org/glob-parent/-/glob-parent-5.1.2.tgz#869832c58034fe68a4093c17dc15e8340d8401c4" + integrity sha512-AOIgSQCepiJYwP3ARnGx+5VnTu2HBYdzbGP45eLw1vr3zB3vZLeyed1sC9hnbcOc9/SrMyM5RPQrkGz4aS9Zow== + dependencies: + is-glob "^4.0.1" + +glob-parent@^6.0.2: + version "6.0.2" + resolved "https://registry.npmjs.org/glob-parent/-/glob-parent-6.0.2.tgz#6d237d99083950c79290f24c7642a3de9a28f9e3" + integrity sha512-XxwI8EOhVQgWp6iDL+3b0r86f4d6AX6zSU55HfB4ydCEuXLXc5FcYeOu+nnGftS4TEju/11rt4KJPTMgbfmv4A== + dependencies: + is-glob "^4.0.3" + +globals@^13.23.0: + version "13.24.0" + resolved "https://registry.npmjs.org/globals/-/globals-13.24.0.tgz#8432a19d78ce0c1e833949c36adb345400bb1171" + integrity sha512-AhO5QUcj8llrbG09iWhPU2B204J1xnPeL8kQmVorSsy+Sjj1sk8gIyh6cUocGmH4L0UuhAJy+hJMRA4mgA4mFQ== + dependencies: + type-fest "^0.20.2" + +globals@^14.0.0: + version "14.0.0" + resolved "https://registry.npmjs.org/globals/-/globals-14.0.0.tgz#898d7413c29babcf6bafe56fcadded858ada724e" + integrity sha512-oahGvuMGQlPw/ivIYBjVSrWAfWLBeku5tpPE2fOPLi+WHffIWbuh2tCjhyQhTBPMf5E9jDEH4FOmTYgYwbKwtQ== + +graphemer@^1.4.0: + version "1.4.0" + resolved "https://registry.npmjs.org/graphemer/-/graphemer-1.4.0.tgz#fb2f1d55e0e3a1849aeffc90c4fa0dd53a0e66c6" + integrity sha512-EtKwoO6kxCL9WO5xipiHTZlSzBm7WLT627TqC/uVRd0HKmq8NXyebnNYxDoBi7wt8eTWrUrKXCOVaFq9x1kgag== + +has-flag@^4.0.0: + version "4.0.0" + resolved "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz#944771fd9c81c81265c4d6941860da06bb59479b" + integrity sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ== + html-encoding-sniffer@^4.0.0: version "4.0.0" resolved "https://registry.npmjs.org/html-encoding-sniffer/-/html-encoding-sniffer-4.0.0.tgz" @@ -121,11 +643,58 @@ iconv-lite@0.6.3: dependencies: safer-buffer ">= 2.1.2 < 3.0.0" +ignore@^5.2.0, ignore@^5.3.1: + version "5.3.2" + resolved "https://registry.npmjs.org/ignore/-/ignore-5.3.2.tgz#3cd40e729f3643fd87cb04e50bf0eb722bc596f5" + integrity sha512-hsBTNUqQTDwkWtcdYI2i06Y/nUBEsNEDJKjWdigLvegy8kDuJAS8uRlpkkcQpyEXL0Z/pjDy5HBmMjRCJ2gq+g== + +import-fresh@^3.2.1: + version "3.3.0" + resolved "https://registry.npmjs.org/import-fresh/-/import-fresh-3.3.0.tgz#37162c25fcb9ebaa2e6e53d5b4d88ce17d9e0c2b" + integrity sha512-veYYhQa+D1QBKznvhUHxb8faxlrwUnxseDAbAp457E0wLNio2bOSKnjYDhMj+YiAq61xrMGhQk9iXVk5FzgQMw== + dependencies: + parent-module "^1.0.0" + resolve-from "^4.0.0" + +imurmurhash@^0.1.4: + version "0.1.4" + resolved "https://registry.npmjs.org/imurmurhash/-/imurmurhash-0.1.4.tgz#9218b9b2b928a238b13dc4fb6b6d576f231453ea" + integrity sha512-JmXMZ6wuvDmLiHEml9ykzqO6lwFbof0GG4IkcGaENdCRDDmMVnny7s5HsIgHCbaq0w2MyPhDqkhTUgS2LU2PHA== + +is-extglob@^2.1.1: + version "2.1.1" + resolved "https://registry.npmjs.org/is-extglob/-/is-extglob-2.1.1.tgz#a88c02535791f02ed37c76a1b9ea9773c833f8c2" + integrity sha512-SbKbANkN603Vi4jEZv49LeVJMn4yGwsbzZworEoyEiutsN3nJYdbO36zfhGJ6QEDpOZIFkDtnq5JRxmvl3jsoQ== + +is-glob@^4.0.0, is-glob@^4.0.1, is-glob@^4.0.3: + version "4.0.3" + resolved "https://registry.npmjs.org/is-glob/-/is-glob-4.0.3.tgz#64f61e42cbbb2eec2071a9dac0b28ba1e65d5084" + integrity sha512-xelSayHH36ZgE7ZWhli7pW34hNbNl8Ojv5KVmkJD4hBdD3th8Tfk9vYasLM+mXWOZhFkgZfxhLSnrwRr4elSSg== + dependencies: + is-extglob "^2.1.1" + +is-number@^7.0.0: + version "7.0.0" + resolved "https://registry.npmjs.org/is-number/-/is-number-7.0.0.tgz#7535345b896734d5f80c4d06c50955527a14f12b" + integrity sha512-41Cifkg6e8TylSpdtTpeLVMqvSBEVzTttHvERD741+pnZ8ANv0004MRL43QKPDlK9cGvNp6NZWZUBlbGXYxxng== + is-potential-custom-element-name@^1.0.1: version "1.0.1" resolved "https://registry.npmjs.org/is-potential-custom-element-name/-/is-potential-custom-element-name-1.0.1.tgz" integrity sha512-bCYeRA2rVibKZd+s2625gGnGF/t7DSqDs4dP7CrLA1m7jKWz6pps0LpYLJN8Q64HtmPKJ1hrN3nzPNKFEKOUiQ== +isexe@^2.0.0: + version "2.0.0" + resolved "https://registry.npmjs.org/isexe/-/isexe-2.0.0.tgz#e8fbf374dc556ff8947a10dcb0572d633f2cfa10" + integrity sha512-RHxMLp9lnKHGHRng9QFhRCMbYAcVpn69smSGcq3f36xjgVVWThj4qqLbTLlq7Ssj8B+fIQ1EuCEGI2lKsyQeIw== + +js-yaml@^4.1.0: + version "4.1.0" + resolved "https://registry.npmjs.org/js-yaml/-/js-yaml-4.1.0.tgz#c1fb65f8f5017901cdd2c951864ba18458a10602" + integrity sha512-wpxZs9NoxZaJESJGIZTyDEaYpl0FKSA+FB9aJiyemKhMwkxQg63h4T1KJgUGHpTqPDNRcmmYLugrRjJlBtWvRA== + dependencies: + argparse "^2.0.1" + jsdom@^25.0.1: version "25.0.1" resolved "https://registry.npmjs.org/jsdom/-/jsdom-25.0.1.tgz" @@ -153,6 +722,61 @@ jsdom@^25.0.1: ws "^8.18.0" xml-name-validator "^5.0.0" +json-buffer@3.0.1: + version "3.0.1" + resolved "https://registry.npmjs.org/json-buffer/-/json-buffer-3.0.1.tgz#9338802a30d3b6605fbe0613e094008ca8c05a13" + integrity sha512-4bV5BfR2mqfQTJm+V5tPPdf+ZpuhiIvTuAB5g8kcrXOZpTT/QwwVRWBywX1ozr6lEuPdbHxwaJlm9G6mI2sfSQ== + +json-schema-traverse@^0.4.1: + version "0.4.1" + resolved "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-0.4.1.tgz#69f6a87d9513ab8bb8fe63bdb0979c448e684660" + integrity sha512-xbbCH5dCYU5T8LcEhhuh7HJ88HXuW3qsI3Y0zOZFKfZEHcpWiHU/Jxzk629Brsab/mMiHQti9wMP+845RPe3Vg== + +json-stable-stringify-without-jsonify@^1.0.1: + version "1.0.1" + resolved "https://registry.npmjs.org/json-stable-stringify-without-jsonify/-/json-stable-stringify-without-jsonify-1.0.1.tgz#9db7b59496ad3f3cfef30a75142d2d930ad72651" + integrity sha512-Bdboy+l7tA3OGW6FjyFHWkP5LuByj1Tk33Ljyq0axyzdk9//JSi2u3fP1QSmd1KNwq6VOKYGlAu87CisVir6Pw== + +keyv@^4.5.4: + version "4.5.4" + resolved "https://registry.npmjs.org/keyv/-/keyv-4.5.4.tgz#a879a99e29452f942439f2a405e3af8b31d4de93" + integrity sha512-oxVHkHR/EJf2CNXnWxRLW6mg7JyCCUcG0DtEGmL2ctUo1PNTin1PUil+r/+4r5MpVgC/fn1kjsx7mjSujKqIpw== + dependencies: + json-buffer "3.0.1" + +levn@^0.4.1: + version "0.4.1" + resolved "https://registry.npmjs.org/levn/-/levn-0.4.1.tgz#ae4562c007473b932a6200d403268dd2fffc6ade" + integrity sha512-+bT2uH4E5LGE7h/n3evcS/sQlJXCpIp6ym8OWJ5eV6+67Dsql/LaaT7qJBAt2rzfoa/5QBGBhxDix1dMt2kQKQ== + dependencies: + prelude-ls "^1.2.1" + type-check "~0.4.0" + +locate-path@^6.0.0: + version "6.0.0" + resolved "https://registry.npmjs.org/locate-path/-/locate-path-6.0.0.tgz#55321eb309febbc59c4801d931a72452a681d286" + integrity sha512-iPZK6eYjbxRu3uB4/WZ3EsEIMJFMqAoopl3R+zuq0UjcAm/MO6KCweDgPfP3elTztoKP3KtnVHxTn2NHBSDVUw== + dependencies: + p-locate "^5.0.0" + +lodash.merge@^4.6.2: + version "4.6.2" + resolved "https://registry.npmjs.org/lodash.merge/-/lodash.merge-4.6.2.tgz#558aa53b43b661e1925a0afdfa36a9a1085fe57a" + integrity sha512-0KpjqXRVvrYyCsX1swR/XTK0va6VQkQM6MNo7PqW77ByjAhoARA8EfrP1N4+KlKj8YS0ZUCtRT/YUuhyYDujIQ== + +merge2@^1.3.0: + version "1.4.1" + resolved "https://registry.npmjs.org/merge2/-/merge2-1.4.1.tgz#4368892f885e907455a6fd7dc55c0c9d404990ae" + integrity sha512-8q7VEgMJW4J8tcfVPy8g09NcQwZdbwFEqhe/WZkoIzjn/3TGDwtOCYtXGxA3O8tPzpczCCDgv+P2P5y00ZJOOg== + +micromatch@^4.0.4: + version "4.0.8" + resolved "https://registry.npmjs.org/micromatch/-/micromatch-4.0.8.tgz#d66fa18f3a47076789320b9b1af32bd86d9fa202" + integrity sha512-PXwfBhYu0hBCPw8Dn0E+WDYb7af3dSLVWKi3HGv84IdF4TyFoC0ysxFd0Goxw7nSv4T/PzEJQxsYsEiFCKo2BA== + dependencies: + braces "^3.0.3" + picomatch "^2.3.1" + mime-db@1.52.0: version "1.52.0" resolved "https://registry.npmjs.org/mime-db/-/mime-db-1.52.0.tgz" @@ -165,16 +789,68 @@ mime-types@^2.1.12: dependencies: mime-db "1.52.0" +minimatch@^3.1.2: + version "3.1.2" + resolved "https://registry.npmjs.org/minimatch/-/minimatch-3.1.2.tgz#19cd194bfd3e428f049a70817c038d89ab4be35b" + integrity sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw== + dependencies: + brace-expansion "^1.1.7" + +minimatch@^9.0.4: + version "9.0.5" + resolved "https://registry.npmjs.org/minimatch/-/minimatch-9.0.5.tgz#d74f9dd6b57d83d8e98cfb82133b03978bc929e5" + integrity sha512-G6T0ZX48xgozx7587koeX9Ys2NYy6Gmv//P89sEte9V9whIapMNF4idKxnW2QtCcLiTWlb/wfCabAtAFWhhBow== + dependencies: + brace-expansion "^2.0.1" + ms@^2.1.3: version "2.1.3" resolved "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz" integrity sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA== +natural-compare@^1.4.0: + version "1.4.0" + resolved "https://registry.npmjs.org/natural-compare/-/natural-compare-1.4.0.tgz#4abebfeed7541f2c27acfb29bdbbd15c8d5ba4f7" + integrity sha512-OWND8ei3VtNC9h7V60qff3SVobHr996CTwgxubgyQYEpg290h9J0buyECNNJexkFm5sOajh5G116RYA1c8ZMSw== + nwsapi@^2.2.12: version "2.2.12" resolved "https://registry.npmjs.org/nwsapi/-/nwsapi-2.2.12.tgz" integrity sha512-qXDmcVlZV4XRtKFzddidpfVP4oMSGhga+xdMc25mv8kaLUHtgzCDhUxkrN8exkGdTlLNaXj7CV3GtON7zuGZ+w== +optionator@^0.9.3: + version "0.9.4" + resolved "https://registry.npmjs.org/optionator/-/optionator-0.9.4.tgz#7ea1c1a5d91d764fb282139c88fe11e182a3a734" + integrity sha512-6IpQ7mKUxRcZNLIObR0hz7lxsapSSIYNZJwXPGeF0mTVqGKFIXj1DQcMoT22S3ROcLyY/rz0PWaWZ9ayWmad9g== + dependencies: + deep-is "^0.1.3" + fast-levenshtein "^2.0.6" + levn "^0.4.1" + prelude-ls "^1.2.1" + type-check "^0.4.0" + word-wrap "^1.2.5" + +p-limit@^3.0.2: + version "3.1.0" + resolved "https://registry.npmjs.org/p-limit/-/p-limit-3.1.0.tgz#e1daccbe78d0d1388ca18c64fea38e3e57e3706b" + integrity sha512-TYOanM3wGwNGsZN2cVTYPArw454xnXj5qmWF1bEoAc4+cU/ol7GVh7odevjp1FNHduHc3KZMcFduxU5Xc6uJRQ== + dependencies: + yocto-queue "^0.1.0" + +p-locate@^5.0.0: + version "5.0.0" + resolved "https://registry.npmjs.org/p-locate/-/p-locate-5.0.0.tgz#83c8315c6785005e3bd021839411c9e110e6d834" + integrity sha512-LaNjtRWUBY++zB5nE/NwcaoMylSPk+S+ZHNB1TzdbMJMny6dynpAGt7X/tl/QYq3TIeE6nxHppbo2LGymrG5Pw== + dependencies: + p-limit "^3.0.2" + +parent-module@^1.0.0: + version "1.0.1" + resolved "https://registry.npmjs.org/parent-module/-/parent-module-1.0.1.tgz#691d2709e78c79fae3a156622452d00762caaaa2" + integrity sha512-GQ2EWRpQV8/o+Aw8YqtfZZPfNRWZYkbidE9k5rpl/hC3vtHHBfGm2Ifi6qWV+coDGkrUKZAxE3Lot5kcsRlh+g== + dependencies: + callsites "^3.0.0" + parse5@^7.1.2: version "7.1.2" resolved "https://registry.npmjs.org/parse5/-/parse5-7.1.2.tgz" @@ -182,6 +858,21 @@ parse5@^7.1.2: dependencies: entities "^4.4.0" +path-exists@^4.0.0: + version "4.0.0" + resolved "https://registry.npmjs.org/path-exists/-/path-exists-4.0.0.tgz#513bdbe2d3b95d7762e8c1137efa195c6c61b5b3" + integrity sha512-ak9Qy5Q7jYb2Wwcey5Fpvg2KoAc/ZIhLSLOSBmRmygPsGwkVVt0fZa0qrtMz+m6tJTAHfZQ8FnmB4MG4LWy7/w== + +path-key@^3.1.0: + version "3.1.1" + resolved "https://registry.npmjs.org/path-key/-/path-key-3.1.1.tgz#581f6ade658cbba65a0d3380de7753295054f375" + integrity sha512-ojmeN0qd+y0jszEtoY48r0Peq5dwMEkIlCOu6Q5f41lfkswXuKtYrhgoTpLnyIcHm24Uhqx+5Tqm2InSwLhE6Q== + +picomatch@^2.3.1: + version "2.3.1" + resolved "https://registry.npmjs.org/picomatch/-/picomatch-2.3.1.tgz#3ba3833733646d9d3e4995946c1365a67fb07a42" + integrity sha512-JU3teHTNjmE2VCGFzuY8EXzCDVwEqB2a8fsIvwaStHhAWJEeVd1o1QD80CU6+ZdEXXSLbSsuLwJjkCBWqRQUVA== + playwright-core@1.48.2: version "1.48.2" resolved "https://registry.npmjs.org/playwright-core/-/playwright-core-1.48.2.tgz" @@ -196,16 +887,48 @@ playwright@1.48.2: optionalDependencies: fsevents "2.3.2" -punycode@^2.3.1: +prelude-ls@^1.2.1: + version "1.2.1" + resolved "https://registry.npmjs.org/prelude-ls/-/prelude-ls-1.2.1.tgz#debc6489d7a6e6b0e7611888cec880337d316396" + integrity sha512-vkcDPrRZo1QZLbn5RLGPpg/WmIQ65qoWWhcGKf/b5eplkkarX0m9z8ppCat4mlOqUsWpyNuYgO3VRyrYHSzX5g== + +prettier@3.4.2: + version "3.4.2" + resolved "https://registry.npmjs.org/prettier/-/prettier-3.4.2.tgz#a5ce1fb522a588bf2b78ca44c6e6fe5aa5a2b13f" + integrity sha512-e9MewbtFo+Fevyuxn/4rrcDAaq0IYxPGLvObpQjiZBMAzB9IGmzlnG9RZy3FFas+eBMu2vA0CszMeduow5dIuQ== + +punycode@^2.1.0, punycode@^2.3.1: version "2.3.1" resolved "https://registry.npmjs.org/punycode/-/punycode-2.3.1.tgz" integrity sha512-vYt7UD1U9Wg6138shLtLOvdAu+8DsC/ilFtEVHcH+wydcSpNE20AfSOduf6MkRFahL5FY7X1oU7nKVZFtfq8Fg== +queue-microtask@^1.2.2: + version "1.2.3" + resolved "https://registry.npmjs.org/queue-microtask/-/queue-microtask-1.2.3.tgz#4929228bbc724dfac43e0efb058caf7b6cfb6243" + integrity sha512-NuaNSa6flKT5JaSYQzJok04JzTL1CA6aGhv5rfLW3PgqA+M2ChpZQnAC8h8i4ZFkBS8X5RqkDBHA7r4hej3K9A== + +resolve-from@^4.0.0: + version "4.0.0" + resolved "https://registry.npmjs.org/resolve-from/-/resolve-from-4.0.0.tgz#4abcd852ad32dd7baabfe9b40e00a36db5f392e6" + integrity sha512-pb/MYmXstAkysRFx8piNI1tGFNQIFA3vkE3Gq4EuA1dF6gHp/+vgZqsCGJapvy8N3Q+4o7FwvquPJcnZ7RYy4g== + +reusify@^1.0.4: + version "1.0.4" + resolved "https://registry.npmjs.org/reusify/-/reusify-1.0.4.tgz#90da382b1e126efc02146e90845a88db12925d76" + integrity sha512-U9nH88a3fc/ekCF1l0/UP1IosiuIjyTh7hBvXVMHYgVcfGvt897Xguj2UOLDeI5BG2m7/uwyaLVT6fbtCwTyzw== + rrweb-cssom@^0.7.1: version "0.7.1" resolved "https://registry.npmjs.org/rrweb-cssom/-/rrweb-cssom-0.7.1.tgz" integrity sha512-TrEMa7JGdVm0UThDJSx7ddw5nVm3UJS9o9CCIZ72B1vSyEZoziDqBYP3XIoi/12lKrJR8rE3jeFHMok2F/Mnsg== +run-parallel@^1.1.9: + version "1.2.0" + resolved "https://registry.npmjs.org/run-parallel/-/run-parallel-1.2.0.tgz#66d1368da7bdf921eb9d95bd1a9229e7f21a43ee" + integrity sha512-5l4VyZR86LZ/lDxZTR6jqL8AFE2S0IFLMP26AbjsLVADxHdhB/c0GUsH+y39UfCi3dzz8OlQuPmnaJOMoDHQBA== + dependencies: + queue-microtask "^1.2.2" + "safer-buffer@>= 2.1.2 < 3.0.0": version "2.1.2" resolved "https://registry.npmjs.org/safer-buffer/-/safer-buffer-2.1.2.tgz" @@ -223,6 +946,35 @@ saxes@^6.0.0: dependencies: xmlchars "^2.2.0" +semver@^7.6.0: + version "7.6.3" + resolved "https://registry.npmjs.org/semver/-/semver-7.6.3.tgz#980f7b5550bc175fb4dc09403085627f9eb33143" + integrity sha512-oVekP1cKtI+CTDvHWYFUcMtsK/00wmAEfyqKfNdARm8u1wNVhSgaX7A8d4UuIlUI5e84iEwOhs7ZPYRmzU9U6A== + +shebang-command@^2.0.0: + version "2.0.0" + resolved "https://registry.npmjs.org/shebang-command/-/shebang-command-2.0.0.tgz#ccd0af4f8835fbdc265b82461aaf0c36663f34ea" + integrity sha512-kHxr2zZpYtdmrN1qDjrrX/Z1rR1kG8Dx+gkpK1G4eXmvXswmcE1hTWBWYUzlraYw1/yZp6YuDY77YtvbN0dmDA== + dependencies: + shebang-regex "^3.0.0" + +shebang-regex@^3.0.0: + version "3.0.0" + resolved "https://registry.npmjs.org/shebang-regex/-/shebang-regex-3.0.0.tgz#ae16f1644d873ecad843b0307b143362d4c42172" + integrity sha512-7++dFhtcx3353uBaq8DDR4NuxBetBzC7ZQOhmTQInHEd6bSrXdiEyzCvG07Z44UYdLShWUyXt5M/yhz8ekcb1A== + +strip-json-comments@^3.1.1: + version "3.1.1" + resolved "https://registry.npmjs.org/strip-json-comments/-/strip-json-comments-3.1.1.tgz#31f1281b3832630434831c310c01cccda8cbe006" + integrity sha512-6fPc+R4ihwqP6N/aIv2f1gMH8lOVtWQHoqC4yK6oSDVVocumAsfCqjkXnqiYMhmMwS/mEHLp7Vehlt3ql6lEig== + +supports-color@^7.1.0: + version "7.2.0" + resolved "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz#1b7dcdcb32b8138801b3e478ba6a51caa89648da" + integrity sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw== + dependencies: + has-flag "^4.0.0" + symbol-tree@^3.2.4: version "3.2.4" resolved "https://registry.npmjs.org/symbol-tree/-/symbol-tree-3.2.4.tgz" @@ -240,6 +992,13 @@ tldts@^6.1.32: dependencies: tldts-core "^6.1.47" +to-regex-range@^5.0.1: + version "5.0.1" + resolved "https://registry.npmjs.org/to-regex-range/-/to-regex-range-5.0.1.tgz#1648c44aae7c8d988a326018ed72f5b4dd0392e4" + integrity sha512-65P7iz6X5yEr1cwcgvQxbbIw7Uk3gOy5dIdtZ4rDveLqhrdJP+Li/Hx6tyK0NEb+2GCyneCMJiGqrADCSNk8sQ== + dependencies: + is-number "^7.0.0" + tough-cookie@^5.0.0: version "5.0.0" resolved "https://registry.npmjs.org/tough-cookie/-/tough-cookie-5.0.0.tgz" @@ -254,11 +1013,49 @@ tr46@^5.0.0: dependencies: punycode "^2.3.1" +ts-api-utils@^1.3.0: + version "1.4.3" + resolved "https://registry.npmjs.org/ts-api-utils/-/ts-api-utils-1.4.3.tgz#bfc2215fe6528fecab2b0fba570a2e8a4263b064" + integrity sha512-i3eMG77UTMD0hZhgRS562pv83RC6ukSAC2GMNWc+9dieh/+jDM5u5YG+NHX6VNDRHQcHwmsTHctP9LhbC3WxVw== + +type-check@^0.4.0, type-check@~0.4.0: + version "0.4.0" + resolved "https://registry.npmjs.org/type-check/-/type-check-0.4.0.tgz#07b8203bfa7056c0657050e3ccd2c37730bab8f1" + integrity sha512-XleUoc9uwGXqjWwXaUTZAmzMcFZ5858QA2vvx1Ur5xIcixXIP+8LnFDgRplU30us6teqdlskFfu+ae4K79Ooew== + dependencies: + prelude-ls "^1.2.1" + +type-fest@^0.20.2: + version "0.20.2" + resolved "https://registry.npmjs.org/type-fest/-/type-fest-0.20.2.tgz#1bf207f4b28f91583666cb5fbd327887301cd5f4" + integrity sha512-Ne+eE4r0/iWnpAxD852z3A+N0Bt5RN//NjJwRd2VFHEmrywxf5vsZlh4R6lixl6B+wz/8d+maTSAkN1FIkI3LQ== + +typescript-eslint@^8.19.0: + version "8.19.0" + resolved "https://registry.npmjs.org/typescript-eslint/-/typescript-eslint-8.19.0.tgz#e4ff06b19f2f9807a2c26147a0199a109944d9e0" + integrity sha512-Ni8sUkVWYK4KAcTtPjQ/UTiRk6jcsuDhPpxULapUDi8A/l8TSBk+t1GtJA1RsCzIJg0q6+J7bf35AwQigENWRQ== + dependencies: + "@typescript-eslint/eslint-plugin" "8.19.0" + "@typescript-eslint/parser" "8.19.0" + "@typescript-eslint/utils" "8.19.0" + +typescript@^5.7.2: + version "5.7.2" + resolved "https://registry.npmjs.org/typescript/-/typescript-5.7.2.tgz#3169cf8c4c8a828cde53ba9ecb3d2b1d5dd67be6" + integrity sha512-i5t66RHxDvVN40HfDd1PsEThGNnlMCMT3jMUuoh9/0TaqWevNontacunWyN02LA9/fIbEWlcHZcgTKb9QoaLfg== + undici-types@~6.19.2: version "6.19.8" resolved "https://registry.npmjs.org/undici-types/-/undici-types-6.19.8.tgz" integrity sha512-ve2KP6f/JnbPBFyobGHuerC9g1FYGn/F8n1LWTwNxCEzd6IfqTwUQcNXgEtmmQ6DlRrC1hrSrBnCZPokRrDHjw== +uri-js@^4.2.2: + version "4.4.1" + resolved "https://registry.npmjs.org/uri-js/-/uri-js-4.4.1.tgz#9b1a52595225859e55f669d928f88c6c57f2a77e" + integrity sha512-7rKUyy33Q1yc98pQ1DAmLtwX109F7TIfWlW1Ydo8Wl1ii1SeHieeh0HHfPeL2fMXK6z0s8ecKs9frCuLJvndBg== + dependencies: + punycode "^2.1.0" + w3c-xmlserializer@^5.0.0: version "5.0.0" resolved "https://registry.npmjs.org/w3c-xmlserializer/-/w3c-xmlserializer-5.0.0.tgz" @@ -291,6 +1088,18 @@ whatwg-url@^14.0.0: tr46 "^5.0.0" webidl-conversions "^7.0.0" +which@^2.0.1: + version "2.0.2" + resolved "https://registry.npmjs.org/which/-/which-2.0.2.tgz#7c6a8dd0a636a0327e10b59c9286eee93f3f51b1" + integrity sha512-BLI3Tl1TW3Pvl70l3yq3Y64i+awpwXqsGBYWkkqMtnbXgrMD+yj7rhW0kuEDxzJaYXGjEW5ogapKNMEKNMjibA== + dependencies: + isexe "^2.0.0" + +word-wrap@^1.2.5: + version "1.2.5" + resolved "https://registry.npmjs.org/word-wrap/-/word-wrap-1.2.5.tgz#d2c45c6dd4fbce621a66f136cbe328afd0410b34" + integrity sha512-BN22B5eaMMI9UMtjrGd5g5eCYPpCPDUy0FJXbYsaT5zYxjFOckS53SQDE3pWkVoWpHXVb3BrYcEN4Twa55B5cA== + ws@^8.18.0: version "8.18.0" resolved "https://registry.npmjs.org/ws/-/ws-8.18.0.tgz" @@ -318,3 +1127,8 @@ xmlchars@^2.2.0: version "2.2.0" resolved "https://registry.npmjs.org/xmlchars/-/xmlchars-2.2.0.tgz" integrity sha512-JZnDKK8B0RCDw84FNdDAIpZK+JuJw+s7Lz8nksI7SIuU3UXJJslUthsi+uWBUYOwPFwW7W7PRLRfUKpxjtjFCw== + +yocto-queue@^0.1.0: + version "0.1.0" + resolved "https://registry.npmjs.org/yocto-queue/-/yocto-queue-0.1.0.tgz#0294eb3dee05028d31ee1a5fa2c556a6aaf10a1b" + integrity sha512-rVksvsnNCdJ/ohGc6xgPwyN8eheCxsiLM8mxuE/t/mOVqJewPuO1miLpTHQiRgTKCLexL4MeAFVagts7HmNZ2Q==