diff --git a/packages/core/src/components/Modal/ModalTopActions/ModalTopActions.tsx b/packages/core/src/components/Modal/ModalTopActions/ModalTopActions.tsx index e381325439..b391481777 100644 --- a/packages/core/src/components/Modal/ModalTopActions/ModalTopActions.tsx +++ b/packages/core/src/components/Modal/ModalTopActions/ModalTopActions.tsx @@ -4,6 +4,7 @@ import { ModalTopActionsButtonColor, ModalTopActionsTheme, ModalTopActionsProps import IconButton from "../../IconButton/IconButton"; import { CloseMedium } from "@vibe/icons"; import { ButtonColor } from "../../Button/ButtonConstants"; +import { ComponentDefaultTestId } from "../../../tests/constants"; const colorToButtonColor: Record = { dark: ButtonColor.ON_INVERTED_BACKGROUND, @@ -17,6 +18,7 @@ const ModalTopActions = ({ renderAction, theme, closeButtonAriaLabel, onClose }:
{typeof renderAction === "function" ? renderAction(buttonColor) : renderAction} { test("should initialize buttons if needed", async () => { // Initialize the buttons inside the ButtonGroup - await buttonGroup.initializeButtonsIfNeeded(); - - // Verify that buttons are initialized - expect(buttonGroup.buttonsInitialized).toBe(true); - + const buttons = await buttonGroup.getAllButtons(); // Verify that buttons exist in the items array - expect(buttonGroup.items.length).toBeGreaterThan(0); // Ensure that buttons were initialized + expect(buttons.length).toBeGreaterThan(0); }); test("should retrieve a button by name and click it", async ({ page }) => { // Initialize the buttons inside the ButtonGroup - await buttonGroup.initializeButtonsIfNeeded(); - const button = await buttonGroup.getButtonByName("Beta"); // Adjust the button name as needed // Add a listener for console logs to capture the click event @@ -58,7 +52,7 @@ test.describe("ButtonGroup Class with Storybook", () => { }); // Click the button - await buttonGroup.click("Beta"); + await buttonGroup.clickButton("Beta"); // eslint-disable-next-line playwright/no-wait-for-timeout -- Wait a bit to ensure the console log is captured await page.waitForTimeout(500); @@ -66,8 +60,4 @@ test.describe("ButtonGroup Class with Storybook", () => { // Verify the console message contains the expected log expect(consoleMessage).toContain("Button clicked: Beta"); }); - - test("should throw an error if trying to click a non-existent button", async () => { - await expect(buttonGroup.click("NonExistentButton")).rejects.toThrow("Invalid button name provided"); - }); }); diff --git a/packages/testkit/components/BaseElement.ts b/packages/testkit/components/BaseElement.ts index 111df7f66f..f6698ace94 100644 --- a/packages/testkit/components/BaseElement.ts +++ b/packages/testkit/components/BaseElement.ts @@ -27,33 +27,6 @@ export class BaseElement { return this.locator; } - /** - * Wait for the list elements to stabilize (i.e., the count of items remains constant for a specified duration). - * @returns {Promise} - */ - async waitForElementsGroup(locator: Locator, elementReportName: string): Promise { - await test.step(`Wait for ${elementReportName} items to stabilize`, async () => { - let previousCount = 0; - let stableCountTime = 0; - const stabilizationTimeMs = 500; - // eslint-disable-next-line no-constant-condition - while (true) { - const currentCount = await this.locator.locator(locator).count(); - - if (currentCount === previousCount) { - stableCountTime += 100; // Increase stable time by the check interval (100ms) - } else { - stableCountTime = 0; // Reset if the count changes - } - if (stableCountTime >= stabilizationTimeMs) { - break; // Break the loop if count has been stable for the desired duration - } - previousCount = currentCount; - await this.page.waitForTimeout(100); // Polling interval (100ms) - } - }); - } - /** * Check if the element is enabled. * @returns {Promise} - Returns true if the element is enabled, otherwise false. @@ -67,6 +40,26 @@ export class BaseElement { return isEnabled; } + /** + * Hover the element. + * @returns {Promise} + */ + async hover(): Promise { + await test.step(`Hover ${this.elementReportName}`, async () => { + await this.locator.hover(); + }); + } + + /** + * Click the element. + * @returns {Promise} + */ + async click(): Promise { + await test.step(`Click ${this.elementReportName}`, async () => { + await this.locator.click(); + }); + } + /** * Scroll the element into view if needed. * @returns {Promise} @@ -119,6 +112,12 @@ export class BaseElement { }); } + async waitForVisible(): Promise { + await test.step(`Wait for ${this.elementReportName}`, async () => { + await this.locator.waitFor({ state: "visible" }); + }); + } + async waitForAbsence(): Promise { await test.step(`Wait for ${this.elementReportName} to be absent`, async () => { await this.waitFor({ state: "detached" }); @@ -140,4 +139,55 @@ export class BaseElement { }); return isVisible; } + + /** + * Wait for the list elements to stabilize (i.e., the count of items remains constant for a specified duration). + * @param {Locator} locator - The locator for the elements. + * @returns {Promise} + */ + protected async waitForAndVerifyElements(locator: Locator): Promise { + await test.step(`Wait for ${this.elementReportName} items to stabilize and verify existence`, async () => { + let previousCount = 0; + let stableCountTime = 0; + const stabilizationTimeMs = 500; + + // eslint-disable-next-line no-constant-condition + while (true) { + const currentCount = await locator.count(); + + // Verify we have at least one element + if (currentCount === 0) { + await this.page.waitForTimeout(100); + continue; + } + + // Check if all elements are visible + const elements = await locator.all(); + const visibleStates = await Promise.all(elements.map(el => el.isVisible())); + const allVisible = visibleStates.every(state => state === true); + + if (!allVisible) { + await this.page.waitForTimeout(100); + continue; + } + + if (currentCount === previousCount) { + stableCountTime += 100; + } else { + stableCountTime = 0; + } + + if (stableCountTime >= stabilizationTimeMs) { + break; + } + + previousCount = currentCount; + await this.page.waitForTimeout(100); + } + + if ((await locator.count()) === 0) { + throw new Error(`No ${this.elementReportName} elements found after stabilization`); + } + }); + } } diff --git a/packages/testkit/components/Button.ts b/packages/testkit/components/Button.ts index 6a3ec61dca..4e9bcdfe22 100644 --- a/packages/testkit/components/Button.ts +++ b/packages/testkit/components/Button.ts @@ -6,9 +6,6 @@ import { BaseElement } from "./BaseElement"; * Extends the BaseElement class. */ export class Button extends BaseElement { - override page: Page; - override locator: Locator; - override elementReportName: string; /** * Create a Button. * @param {Page} page - The Playwright page object. @@ -17,9 +14,6 @@ export class Button extends BaseElement { */ constructor(page: Page, locator: Locator, elementReportName: string) { super(page, locator, elementReportName); - this.page = page; - this.locator = locator; - this.elementReportName = elementReportName; } /** diff --git a/packages/testkit/components/ButtonGroup.ts b/packages/testkit/components/ButtonGroup.ts index 5d313bf530..8d3127ab56 100644 --- a/packages/testkit/components/ButtonGroup.ts +++ b/packages/testkit/components/ButtonGroup.ts @@ -7,49 +7,24 @@ import { Button } from "./Button"; * Extends the BaseElement class. */ export class ButtonGroup extends BaseElement { - override page: Page; - override locator: Locator; - override elementReportName: string; - items: Button[]; - buttonsInitialized: boolean; - constructor(page: Page, locator: Locator, elementReportName: string) { super(page, locator, elementReportName); - this.page = page; - this.locator = locator; - this.elementReportName = elementReportName; - this.items = []; - this.buttonsInitialized = false; } /** - * Initialize buttons if they are not already initialized. - * @returns {Promise} - */ - async initializeButtonsIfNeeded() { - await test.step(`Initialize ${this.elementReportName} if needed`, async () => { - if (!this.buttonsInitialized) { - await this.initializeButtons(); - this.buttonsInitialized = true; - } - }); - } - - /** - * Initialize the buttons by locating all button elements. - * @returns {Promise} + * Get all buttons in the button group. + * @returns {Promise} An array of Button objects. */ - async initializeButtons() { - await test.step(`Initialize ${this.elementReportName}`, async () => { - await this.waitForElementsGroup(this.locator.locator("button"), this.elementReportName); - const buttonElements = await this.locator.locator("button").all(); - this.items = await Promise.all( - buttonElements.map(async locator => { - const buttonName = await locator.innerText(); - return new Button(this.page, locator.getByText(`${buttonName}`), `Button: ${buttonName}`); - }) + async getAllButtons(): Promise { + let buttons: Button[] = []; + await test.step(`Get all buttons in ${this.elementReportName}`, async () => { + const buttonsLocators = await this.locator.locator("button").all(); + const buttonPromises = buttonsLocators.map( + async buttonLocator => new Button(this.page, buttonLocator, await buttonLocator.innerText()) ); + buttons = await Promise.all(buttonPromises); }); + return buttons; } /** @@ -57,12 +32,16 @@ export class ButtonGroup extends BaseElement { * @param {string} buttonName - The name of the button to retrieve. * @returns {Button} The button with the specified name. */ - getButtonByName(buttonName: string): Button | undefined { - if (!buttonName) { - throw new Error("Invalid button name provided"); - } - - return this.items.find(item => item.elementReportName.includes(buttonName)); + async getButtonByName(buttonName: string): Promise