(ListSelectionMode.Single);
+
+ return (
+ <>
+
+
+
+ setCancelCount((c) => c + 1)}
+ onClose={() => setOpen(false)}
+ selectionMode={selectionMode}
+ >
+ {listItems}
+
+ {cancelCount}
+ >
+ );
+};
+
+export const SelectDialogConfirmButtonPropsTestComp = () => {
+ return (
+
+ );
+};
diff --git a/playwright-ct.config.ts b/playwright-ct.config.ts
new file mode 100644
index 00000000000..1bdca3c4655
--- /dev/null
+++ b/playwright-ct.config.ts
@@ -0,0 +1,45 @@
+import { fileURLToPath } from 'node:url';
+import { defineConfig, devices } from '@playwright/experimental-ct-react';
+import react from '@vitejs/plugin-react';
+import tsconfigPaths from 'vite-tsconfig-paths';
+
+export default defineConfig({
+ testDir: '.',
+ testMatch: ['**/packages/main/src/components/**/test/*.spec.tsx', '**/playwright/test/*.spec.tsx'],
+ testIgnore: ['**/*.cy.tsx', '**/*.cy.ts', '**/*.stories.tsx', '**/*.mdx'],
+ fullyParallel: true,
+ forbidOnly: !!process.env.CI,
+ retries: process.env.CI ? 1 : 0,
+ workers: process.env.CI ? '100%' : undefined,
+ reporter: 'html',
+ timeout: 10_000,
+ expect: { timeout: 4000 },
+ use: {
+ trace: 'on-first-retry',
+ ctViteConfig: {
+ plugins: [
+ react(),
+ tsconfigPaths({
+ projects: [fileURLToPath(new URL('tsconfig.base.json', import.meta.url))],
+ }),
+ ],
+ optimizeDeps: {
+ esbuildOptions: {
+ target: 'esnext',
+ },
+ exclude: ['**/*.cy.tsx', '**/*.cy.ts', '**/*.stories.tsx'],
+ },
+ build: {
+ target: 'esnext',
+ rollupOptions: {
+ external: [/\.cy\.tsx$/, /\.cy\.ts$/, /\.stories\.tsx$/],
+ },
+ },
+ },
+ },
+ projects: [
+ { name: 'chromium', use: { ...devices['Desktop Chrome'] } },
+ { name: 'firefox', use: { ...devices['Desktop Firefox'] } },
+ { name: 'webkit', use: { ...devices['Desktop Safari'] } },
+ ],
+});
diff --git a/playwright/index.html b/playwright/index.html
new file mode 100644
index 00000000000..d11d6018986
--- /dev/null
+++ b/playwright/index.html
@@ -0,0 +1,17 @@
+
+
+
+
+
+ Playwright Component Tests
+
+
+
+
+
+
+
diff --git a/playwright/index.tsx b/playwright/index.tsx
new file mode 100644
index 00000000000..135f5a79c4f
--- /dev/null
+++ b/playwright/index.tsx
@@ -0,0 +1 @@
+import '@ui5/webcomponents-react/dist/Assets.js';
diff --git a/playwright/test/UI5FixturesTestComponents.tsx b/playwright/test/UI5FixturesTestComponents.tsx
new file mode 100644
index 00000000000..f477b09df8d
--- /dev/null
+++ b/playwright/test/UI5FixturesTestComponents.tsx
@@ -0,0 +1,251 @@
+import { Button } from '@ui5/webcomponents-react/Button';
+import { CheckBox } from '@ui5/webcomponents-react/CheckBox';
+import { ComboBox } from '@ui5/webcomponents-react/ComboBox';
+import { ComboBoxItem } from '@ui5/webcomponents-react/ComboBoxItem';
+import { Dialog } from '@ui5/webcomponents-react/Dialog';
+import { Input } from '@ui5/webcomponents-react/Input';
+import { List } from '@ui5/webcomponents-react/List';
+import { ListItemStandard } from '@ui5/webcomponents-react/ListItemStandard';
+import { MultiComboBox } from '@ui5/webcomponents-react/MultiComboBox';
+import { MultiComboBoxItem } from '@ui5/webcomponents-react/MultiComboBoxItem';
+import { MultiInput } from '@ui5/webcomponents-react/MultiInput';
+import { Option } from '@ui5/webcomponents-react/Option';
+import { RadioButton } from '@ui5/webcomponents-react/RadioButton';
+import { Select } from '@ui5/webcomponents-react/Select';
+import { SuggestionItem } from '@ui5/webcomponents-react/SuggestionItem';
+import { Switch } from '@ui5/webcomponents-react/Switch';
+import { Tab } from '@ui5/webcomponents-react/Tab';
+import { TabContainer } from '@ui5/webcomponents-react/TabContainer';
+import { TextArea } from '@ui5/webcomponents-react/TextArea';
+import { Toolbar } from '@ui5/webcomponents-react/Toolbar';
+import { ToolbarButton } from '@ui5/webcomponents-react/ToolbarButton';
+import { useState } from 'react';
+
+export const InputTestComp = () => {
+ const [value, setValue] = useState('');
+ return (
+ <>
+ setValue(e.target.value)} />
+ {value}
+ >
+ );
+};
+
+export const ClearInputTestComp = () => {
+ const [value, setValue] = useState('initial value');
+ return (
+ <>
+ setValue(e.target.value)} />
+ {value}
+ >
+ );
+};
+
+export const CheckboxTestComp = () => {
+ const [checked, setChecked] = useState(false);
+ return (
+
+ setChecked(e.target.checked)} />
+ {checked ? 'checked' : 'unchecked'}
+
+ );
+};
+
+export const SwitchTestComp = () => {
+ const [checked, setChecked] = useState(false);
+ return (
+
+ setChecked(e.target.checked)} />
+ {checked ? 'on' : 'off'}
+
+ );
+};
+
+export const RadioButtonTestComp = () => {
+ const [selected, setSelected] = useState('');
+ return (
+
+ setSelected('option1')} />
+ setSelected('option2')} />
+ {selected}
+
+ );
+};
+
+export const TextAreaTestComp = () => {
+ const [value, setValue] = useState('');
+ return (
+
+
+ );
+};
+
+export const DialogTestComp = () => {
+ const [open, setOpen] = useState(true);
+ return (
+ <>
+
+ {open ? 'open' : 'closed'}
+ >
+ );
+};
+
+export const AttributeTestComp = () => {
+ return ;
+};
+
+export const ListTestComp = () => {
+ const [selectedItem, setSelectedItem] = useState('');
+ return (
+
+ {
+ const item = e.detail.selectedItems[0];
+ if (item) {
+ setSelectedItem(item.getAttribute('text') || '');
+ }
+ }}
+ >
+
+
+
+
+ {selectedItem}
+
+ );
+};
+
+export const SelectTestComp = () => {
+ const [selectedValue, setSelectedValue] = useState('');
+ return (
+
+
+ {selectedValue}
+
+ );
+};
+
+export const ComboBoxTestComp = () => {
+ const [value, setValue] = useState('');
+ return (
+
+ setValue(e.detail.item?.text || '')}>
+
+
+
+
+ {value}
+
+ );
+};
+
+export const MultiComboBoxTestComp = () => {
+ const [selectedItems, setSelectedItems] = useState([]);
+ return (
+
+ {
+ const items = e.detail.items.map((item) => item.text);
+ setSelectedItems(items);
+ }}
+ >
+
+
+
+
+ {selectedItems.join(', ')}
+
+ );
+};
+
+export const ToolbarTestComp = () => {
+ const [clickedButton, setClickedButton] = useState('');
+ return (
+
+
+ setClickedButton('Save')} />
+ setClickedButton('Edit')} />
+ setClickedButton('Delete')} />
+
+ {clickedButton}
+
+ );
+};
+
+export const TabContainerTestComp = () => {
+ const [selectedTab, setSelectedTab] = useState('Tab 1');
+ return (
+
+ setSelectedTab(e.detail.tab.text || '')}>
+ Content 1
+ Content 2
+ Content 3
+
+ {selectedTab}
+
+ );
+};
+
+export const TabContainerWithNestedTabsTestComp = () => {
+ return (
+
+ Content 1
+
+ Content 2.1
+ Content 2.2
+ >
+ }
+ >
+ Content 2
+
+
+ );
+};
+
+export const InputWithDelayTestComp = () => {
+ const [value, setValue] = useState('');
+ return (
+
+ setValue(e.target.value || '')}>
+
+
+
+
+ {value}
+
+ );
+};
+
+export const InputWithSuggestionsTestComp = () => {
+ return (
+
+
+
+
+
+ );
+};
+
+export const MultiInputWithSuggestionsTestComp = () => {
+ return (
+
+
+
+
+
+ );
+};
diff --git a/playwright/test/ui5-fixtures.spec.tsx b/playwright/test/ui5-fixtures.spec.tsx
new file mode 100644
index 00000000000..5111016fcc6
--- /dev/null
+++ b/playwright/test/ui5-fixtures.spec.tsx
@@ -0,0 +1,258 @@
+import { expect, test } from '../ui5-fixtures.js';
+import {
+ AttributeTestComp,
+ ClearInputTestComp,
+ ComboBoxTestComp,
+ DialogTestComp,
+ InputTestComp,
+ InputWithSuggestionsTestComp,
+ MultiComboBoxTestComp,
+ MultiInputWithSuggestionsTestComp,
+ SelectTestComp,
+ TabContainerTestComp,
+ TabContainerWithNestedTabsTestComp,
+ TextAreaTestComp,
+} from './UI5FixturesTestComponents.js';
+
+test.describe('UI5 Web Components Fixtures', () => {
+ test('typeIntoInput - types text into UI5 input', async ({ mount, page, ui5wc }) => {
+ await mount();
+
+ const input = page.getByTestId('test-input');
+ await ui5wc.typeIntoInput(input, 'Hello World');
+
+ await expect(input).toHaveAttribute('value', 'Hello World');
+ await expect(page.getByTestId('input-value')).toHaveText('Hello World');
+ });
+
+ test('clearInput - clears UI5 input', async ({ mount, page, ui5wc }) => {
+ await mount();
+
+ const input = page.getByTestId('test-input');
+ await ui5wc.typeIntoInput(input, 'initial value');
+ await expect(input).toHaveAttribute('value', 'initial value');
+
+ await ui5wc.clearInput(input);
+ await expect(input).toHaveAttribute('value', '');
+ await expect(page.getByTestId('input-value')).toHaveText('');
+ });
+
+ // test('toggleCheckbox - toggles UI5 checkbox', async ({ mount, page, ui5wc }) => {
+ // await mount();
+ //
+ // const checkbox = page.getByTestId('test-checkbox');
+ // await ui5wc.toggleCheckbox(checkbox);
+ // await expect(checkbox).toHaveAttribute('checked');
+ // await expect(page.getByTestId('checkbox-state')).toHaveText('checked');
+ //
+ // await ui5wc.toggleCheckbox(checkbox);
+ // await expect(checkbox).not.toHaveAttribute('checked');
+ // await expect(page.getByTestId('checkbox-state')).toHaveText('unchecked');
+ // });
+ //
+ // test('toggleSwitch - toggles UI5 switch', async ({ mount, page, ui5wc }) => {
+ // await mount();
+ //
+ // const switchEl = page.getByTestId('test-switch');
+ // await expect(switchEl).not.toHaveAttribute('checked');
+ //
+ // await ui5wc.toggleSwitch(switchEl);
+ // await expect(switchEl).toHaveAttribute('checked');
+ // await expect(page.getByTestId('switch-state')).toHaveText('on');
+ // });
+
+ // test('clickRadioButton - clicks UI5 radio button', async ({ mount, page, ui5wc }) => {
+ // await mount();
+ //
+ // const radio1 = page.getByTestId('radio-1');
+ // await expect(radio1).not.toHaveAttribute('checked');
+ //
+ // await ui5wc.clickRadioButton(radio1);
+ // await expect(radio1).toHaveAttribute('checked');
+ // await expect(page.getByTestId('radio-state')).toHaveText('option1');
+ // });
+
+ test('typeIntoTextArea - types text into UI5 textarea', async ({ mount, page, ui5wc }) => {
+ await mount();
+
+ const textarea = page.getByTestId('test-textarea');
+ await ui5wc.typeIntoTextArea(textarea, 'Multi-line\ntext');
+
+ await expect(textarea).toHaveAttribute('value', 'Multi-line\ntext');
+ await expect(page.getByTestId('textarea-value')).toContainText('Multi-line text');
+ });
+
+ test('closePopupWithEsc - closes dialog with Escape key', async ({ mount, page, ui5wc }) => {
+ await mount();
+ const dialog = page.getByTestId('test-dialog');
+ await expect(dialog).toHaveAttribute('open');
+ await expect(page.getByTestId('dialog-state')).toHaveText('open');
+
+ await ui5wc.closePopupWithEsc();
+ await expect(dialog).not.toHaveAttribute('open');
+ await expect(page.getByTestId('dialog-state')).toHaveText('closed');
+ });
+
+ //todo: internal test - check if needed
+ test('shouldNeverHaveAttribute - passes when attribute never appears', async ({ mount, page, ui5wc }) => {
+ await mount();
+
+ const button = page.getByTestId('test-button');
+ await ui5wc.shouldNeverHaveAttribute(button, 'disabled', { observerTime: 300 });
+ });
+
+ // test('findShadowInput - returns the shadow input element', async ({ mount, page, ui5wc }) => {
+ // await mount();
+ //
+ // const input = page.getByTestId('test-input');
+ // const shadowInput = ui5wc.findShadowInput(input);
+ //
+ // await expect(shadowInput).toBeVisible();
+ //
+ // await shadowInput.fill('Direct input');
+ // await expect(page.getByTestId('input-value')).toHaveText('Direct input');
+ // });
+
+ // test('clickListItemByText - clicks list item by text', async ({ mount, page, ui5wc }) => {
+ // await mount();
+ //
+ // const list = page.getByTestId('test-list');
+ // await list.getByText('Second Item').click();
+ //
+ // // await ui5wc.clickListItemByText('Second Item', list);
+ //
+ // await expect(page.getByTestId('selected-item')).toHaveText('Second Item');
+ // });
+
+ test('openDropdownByClick - opens Select dropdown', async ({ mount, page, ui5wc }) => {
+ await mount();
+
+ const select = page.getByTestId('test-select');
+ await ui5wc.openDropdownByClick(select);
+
+ await expect(select.locator('[ui5-option]').filter({ hasText: 'Option B' })).toBeVisible();
+ });
+
+ test('openDropdownByClick - opens ComboBox dropdown', async ({ mount, page, ui5wc }) => {
+ await mount();
+
+ const combobox = page.getByTestId('test-combobox');
+ await ui5wc.openDropdownByClick(combobox);
+
+ await expect(combobox.locator('[text="Apple"]')).toBeVisible();
+ });
+
+ test('openDropdownByClick - opens MultiComboBox dropdown', async ({ mount, page, ui5wc }) => {
+ await mount();
+
+ const multiComboBox = page.getByTestId('test-multicombobox');
+ await ui5wc.openDropdownByClick(multiComboBox);
+
+ await expect(multiComboBox.locator('[text="Red"]')).toBeVisible();
+ });
+
+ test('clickDropdownItemByText - selects ComboBox item', async ({ mount, page, ui5wc }) => {
+ await mount();
+
+ const combobox = page.getByTestId('test-combobox');
+ await ui5wc.openDropdownByClick(combobox);
+ await ui5wc.clickDropdownItemByText(combobox, 'Banana');
+
+ await expect(page.getByTestId('combobox-value')).toHaveText('Banana');
+ });
+
+ test('clickDropdownItemByText - selects Select option', async ({ mount, page, ui5wc }) => {
+ await mount();
+
+ const select = page.getByTestId('test-select');
+ await ui5wc.openDropdownByClick(select);
+ await ui5wc.clickDropdownItemByText(select, 'Option B');
+
+ await expect(page.getByTestId('selected-value')).toHaveText('Option B');
+ });
+
+ test('clickDropdownItemByText - selects MultiComboBox item', async ({ mount, page, ui5wc }) => {
+ await mount();
+
+ const multiComboBox = page.getByTestId('test-multicombobox');
+ await ui5wc.openDropdownByClick(multiComboBox);
+ await ui5wc.clickDropdownItemByText(multiComboBox, 'Green');
+
+ await expect(page.getByTestId('multicombobox-values')).toHaveText('Green');
+ });
+
+ // test('findToolbarButtonByText - finds and clicks toolbar button', async ({ mount, page, ui5wc }) => {
+ // await mount();
+ //
+ // const tbB = page.getByText('Delete');
+ //
+ // await tbB.click();
+ // //
+ // // const editButton = ui5wc.findToolbarButtonByText('Edit');
+ // // await expect(editButton).toBeVisible();
+ // // await editButton.click();
+ // //
+ // // await expect(page.getByTestId('clicked-button')).toHaveText('Edit');
+ // });
+
+ test('findTabByText - finds and clicks tab', async ({ mount, page, ui5wc }) => {
+ await mount();
+
+ const tabContainer = page.getByTestId('test-tabcontainer');
+ const tab2 = ui5wc.findTabByText(tabContainer, 'Tab 2');
+ await expect(tab2).toBeVisible();
+ await tab2.click();
+
+ await expect(page.getByTestId('selected-tab')).toHaveText('Tab 2');
+ });
+
+ test('findTabPopoverButtonByText - opens nested tabs popover', async ({ mount, page, ui5wc }) => {
+ await mount();
+
+ const tabContainer = page.getByTestId('test-tabcontainer-nested');
+ const popoverButton = ui5wc.findTabPopoverButtonByText(tabContainer, 'Tab 2');
+ await popoverButton.click();
+
+ const subTab = tabContainer.locator('[ui5-li-custom]').filter({ hasText: 'Tab 2.2' });
+ await expect(subTab).toBeVisible();
+ await subTab.click();
+
+ await expect(tabContainer.getByText('Content 2.2')).toBeVisible();
+ });
+
+ test('typeIntoInput - shows ComboBox suggestions', async ({ mount, page, ui5wc }) => {
+ await mount();
+
+ const combobox = page.getByTestId('test-combobox');
+ await ui5wc.typeIntoInput(combobox, 'A');
+
+ await expect(combobox.locator('[text="Apple"]')).toBeVisible();
+ });
+
+ test('typeIntoInput - shows Input suggestions', async ({ mount, page, ui5wc }) => {
+ await mount();
+
+ const input = page.getByTestId('test-input-suggestions');
+ await ui5wc.typeIntoInput(input, 'S');
+
+ await expect(input.locator('[text="Suggestion A"]')).toBeVisible();
+ });
+
+ test('typeIntoInput - shows MultiComboBox suggestions', async ({ mount, page, ui5wc }) => {
+ await mount();
+
+ const mcb = page.getByTestId('test-multicombobox');
+ await ui5wc.typeIntoInput(mcb, 'R');
+
+ await expect(mcb.locator('[text="Red"]')).toBeVisible();
+ });
+
+ test('typeIntoInput - shows MultiInput suggestions', async ({ mount, page, ui5wc }) => {
+ await mount();
+
+ const multiInput = page.getByTestId('test-multiinput-suggestions');
+ await ui5wc.typeIntoInput(multiInput, 'X');
+
+ await expect(multiInput.locator('[text="Suggestion X"]')).toBeVisible();
+ });
+});
diff --git a/playwright/ui5-fixtures.ts b/playwright/ui5-fixtures.ts
new file mode 100644
index 00000000000..c76e5d85aa0
--- /dev/null
+++ b/playwright/ui5-fixtures.ts
@@ -0,0 +1,220 @@
+import { test as base, expect } from '@playwright/experimental-ct-react';
+import type { Page, Locator } from '@playwright/test';
+
+export interface UI5WCFixtures {
+ ui5wc: UI5WCHelpers;
+}
+
+export class UI5WCHelpers {
+ constructor(private page: Page) {}
+
+ /**
+ * Types a value into a UI5 input component.
+ * Works with Input, ComboBox, MultiComboBox, MultiInput, DatePicker, etc.
+ *
+ * @example
+ * await ui5wc.typeIntoInput(page.getByTestId('my-input'), 'Hello');
+ * await ui5wc.typeIntoInput(combobox, 'search term');
+ */
+ async typeIntoInput(locator: Locator, text: string): Promise {
+ const shadowInput = locator.locator('input');
+ await shadowInput.fill(text);
+ }
+
+ /**
+ * Clears a UI5 input component.
+ *
+ * @example
+ * await ui5wc.clearInput(page.getByTestId('my-input'));
+ */
+ async clearInput(locator: Locator): Promise {
+ const shadowInput = locator.locator('input');
+ await shadowInput.click();
+ await shadowInput.clear();
+ }
+
+ /**
+ * Types a value into a UI5 TextArea component.
+ *
+ * @example
+ * await ui5wc.typeIntoTextArea(page.getByTestId('my-textarea'), 'Multi\nline');
+ */
+ async typeIntoTextArea(locator: Locator, text: string): Promise {
+ const shadowTextarea = locator.locator('textarea');
+ await shadowTextarea.fill(text);
+ }
+
+ /**
+ * Closes an open popup by pressing Escape.
+ *
+ * @example
+ * await ui5wc.closePopupWithEsc();
+ */
+ async closePopupWithEsc(): Promise {
+ await this.page.keyboard.press('Escape');
+ }
+
+ /**
+ * Opens a dropdown by clicking its arrow icon.
+ * Works with Select, ComboBox, and MultiComboBox.
+ *
+ * @example
+ * await ui5wc.openDropdownByClick(page.getByTestId('my-select'));
+ */
+ async openDropdownByClick(locator: Locator): Promise {
+ const inputIcon = locator.locator('.inputIcon');
+ await inputIcon.click();
+ }
+
+ /**
+ * Selects a dropdown item by its text. Must be called after opening the dropdown.
+ * Works with Select, ComboBox, and MultiComboBox.
+ *
+ * @example
+ * await ui5wc.openDropdownByClick(select);
+ * await ui5wc.clickDropdownItemByText(select, 'Option 1');
+ */
+ async clickDropdownItemByText(dropdown: Locator, text: string): Promise {
+ await expect(dropdown.locator('[ui5-responsive-popover]:not([tokenizer-popover])')).toHaveAttribute('open');
+
+ const isSelect = await dropdown.evaluate((el) => el.hasAttribute('ui5-select'));
+ if (isSelect) {
+ const item = dropdown.getByText(text, { exact: true });
+ await item.click();
+ } else {
+ const item = dropdown.locator(`[text="${text}"]`);
+ await item.click();
+ }
+ }
+
+ /**
+ * Finds a tab in a TabContainer by its text.
+ *
+ * @example
+ * const tab = ui5wc.findTabByText(tabContainer, 'Settings');
+ * await tab.click();
+ */
+ findTabByText(tabContainer: Locator, text: string): Locator {
+ return tabContainer.locator('[role="tab"]').filter({ hasText: text });
+ }
+
+ /**
+ * Finds the popover button for a tab with nested sub-tabs.
+ * Only exists when a tab has both content and nested tabs (items prop).
+ *
+ * @example
+ * const btn = ui5wc.findTabPopoverButtonByText(tabContainer, 'Parent Tab');
+ * await btn.click();
+ * await tabContainer.getByText('Child Tab').click();
+ */
+ findTabPopoverButtonByText(tabContainer: Locator, text: string): Locator {
+ return tabContainer.locator('[role="tab"]').filter({ hasText: text }).locator('[ui5-button]');
+ }
+
+ /**
+ * Asserts that an element never gains a specific attribute within an observation period.
+ * Useful for verifying that a button doesn't become disabled during an async operation.
+ *
+ * @example
+ * await ui5wc.shouldNeverHaveAttribute(submitButton, 'disabled', { observerTime: 1000 });
+ */
+ async shouldNeverHaveAttribute(
+ locator: Locator,
+ attributeName: string,
+ options: { observerTime?: number; delayed?: number } = {},
+ ): Promise {
+ const { observerTime = 500, delayed = 0 } = options;
+
+ if (delayed > 0) {
+ await this.page.waitForTimeout(delayed);
+ }
+
+ const element = await locator.elementHandle();
+ if (!element) {
+ throw new Error('Element not found');
+ }
+
+ const attributeFound = await this.page.evaluate(
+ ({ el, attrName, timeout }) => {
+ return new Promise((resolve) => {
+ if (el.hasAttribute(attrName)) {
+ resolve(true);
+ return;
+ }
+
+ const observer = new MutationObserver((mutations) => {
+ for (const mutation of mutations) {
+ if (mutation.attributeName === attrName && el.hasAttribute(attrName)) {
+ observer.disconnect();
+ resolve(true);
+ return;
+ }
+ }
+ });
+
+ observer.observe(el, { attributes: true });
+
+ setTimeout(() => {
+ observer.disconnect();
+ resolve(false);
+ }, timeout);
+ });
+ },
+ { el: element, attrName: attributeName, timeout: observerTime },
+ );
+
+ if (attributeFound) {
+ throw new Error(`Attribute "${attributeName}" was found on element, but it should never appear`);
+ }
+ }
+
+ // REDUNDANT
+
+ findShadowInput(locator: Locator): Locator {
+ return locator.locator('input');
+ }
+
+ async toggleCheckbox(locator: Locator): Promise {
+ await locator.click();
+ }
+
+ async toggleSwitch(locator: Locator): Promise {
+ await locator.click();
+ }
+
+ async clickRadioButton(locator: Locator): Promise {
+ await locator.click();
+ }
+
+ async clickListItemByText(text: string, container?: Locator): Promise {
+ const scope = container ?? this.page;
+ let listItem = scope.locator(`[text="${text}"]`);
+ const count = await listItem.count();
+
+ if (count === 0) {
+ listItem = scope.getByText(text, { exact: true });
+ }
+
+ await listItem.click();
+ }
+
+ findToolbarButtonByText(text: string, container?: Locator): Locator {
+ const scope = container ?? this.page;
+ return scope.locator(`[ui5-toolbar-button][text="${text}"]`);
+ }
+
+ async typeIntoInputWithDelay(locator: Locator, text: string, delay: number = 500): Promise {
+ await this.page.waitForTimeout(delay);
+ const shadowInput = locator.locator('input');
+ await shadowInput.fill(text);
+ }
+}
+
+export const test = base.extend({
+ ui5wc: async ({ page }, use) => {
+ // eslint-disable-next-line react-hooks/rules-of-hooks
+ await use(new UI5WCHelpers(page));
+ },
+});
+
+export { expect };
diff --git a/tsconfig.json b/tsconfig.json
index 3fae03469a6..6e9598966d7 100644
--- a/tsconfig.json
+++ b/tsconfig.json
@@ -25,6 +25,9 @@
{
"path": "./tsconfig.spec.json"
},
+ {
+ "path": "./tsconfig.playwright.json"
+ },
{
"path": "./.storybook/tsconfig.json"
},
diff --git a/tsconfig.playwright.json b/tsconfig.playwright.json
new file mode 100644
index 00000000000..d1e5446b4b2
--- /dev/null
+++ b/tsconfig.playwright.json
@@ -0,0 +1,11 @@
+{
+ "compilerOptions": {
+ "target": "ES2021",
+ "lib": ["es2022", "dom"],
+ "types": ["node"],
+ "moduleResolution": "bundler",
+ "jsx": "react-jsx",
+ "composite": true
+ },
+ "include": ["playwright", "**/*.spec.ts", "**/*.spec.tsx", "playwright-ct.config.ts"]
+}
diff --git a/yarn.lock b/yarn.lock
index f76e5fce858..786f0072df5 100644
--- a/yarn.lock
+++ b/yarn.lock
@@ -1463,6 +1463,13 @@ __metadata:
languageName: node
linkType: hard
+"@esbuild/aix-ppc64@npm:0.25.12":
+ version: 0.25.12
+ resolution: "@esbuild/aix-ppc64@npm:0.25.12"
+ conditions: os=aix & cpu=ppc64
+ languageName: node
+ linkType: hard
+
"@esbuild/aix-ppc64@npm:0.27.3":
version: 0.27.3
resolution: "@esbuild/aix-ppc64@npm:0.27.3"
@@ -1477,6 +1484,13 @@ __metadata:
languageName: node
linkType: hard
+"@esbuild/android-arm64@npm:0.25.12":
+ version: 0.25.12
+ resolution: "@esbuild/android-arm64@npm:0.25.12"
+ conditions: os=android & cpu=arm64
+ languageName: node
+ linkType: hard
+
"@esbuild/android-arm64@npm:0.27.3":
version: 0.27.3
resolution: "@esbuild/android-arm64@npm:0.27.3"
@@ -1491,6 +1505,13 @@ __metadata:
languageName: node
linkType: hard
+"@esbuild/android-arm@npm:0.25.12":
+ version: 0.25.12
+ resolution: "@esbuild/android-arm@npm:0.25.12"
+ conditions: os=android & cpu=arm
+ languageName: node
+ linkType: hard
+
"@esbuild/android-arm@npm:0.27.3":
version: 0.27.3
resolution: "@esbuild/android-arm@npm:0.27.3"
@@ -1505,6 +1526,13 @@ __metadata:
languageName: node
linkType: hard
+"@esbuild/android-x64@npm:0.25.12":
+ version: 0.25.12
+ resolution: "@esbuild/android-x64@npm:0.25.12"
+ conditions: os=android & cpu=x64
+ languageName: node
+ linkType: hard
+
"@esbuild/android-x64@npm:0.27.3":
version: 0.27.3
resolution: "@esbuild/android-x64@npm:0.27.3"
@@ -1519,6 +1547,13 @@ __metadata:
languageName: node
linkType: hard
+"@esbuild/darwin-arm64@npm:0.25.12":
+ version: 0.25.12
+ resolution: "@esbuild/darwin-arm64@npm:0.25.12"
+ conditions: os=darwin & cpu=arm64
+ languageName: node
+ linkType: hard
+
"@esbuild/darwin-arm64@npm:0.27.3":
version: 0.27.3
resolution: "@esbuild/darwin-arm64@npm:0.27.3"
@@ -1533,6 +1568,13 @@ __metadata:
languageName: node
linkType: hard
+"@esbuild/darwin-x64@npm:0.25.12":
+ version: 0.25.12
+ resolution: "@esbuild/darwin-x64@npm:0.25.12"
+ conditions: os=darwin & cpu=x64
+ languageName: node
+ linkType: hard
+
"@esbuild/darwin-x64@npm:0.27.3":
version: 0.27.3
resolution: "@esbuild/darwin-x64@npm:0.27.3"
@@ -1547,6 +1589,13 @@ __metadata:
languageName: node
linkType: hard
+"@esbuild/freebsd-arm64@npm:0.25.12":
+ version: 0.25.12
+ resolution: "@esbuild/freebsd-arm64@npm:0.25.12"
+ conditions: os=freebsd & cpu=arm64
+ languageName: node
+ linkType: hard
+
"@esbuild/freebsd-arm64@npm:0.27.3":
version: 0.27.3
resolution: "@esbuild/freebsd-arm64@npm:0.27.3"
@@ -1561,6 +1610,13 @@ __metadata:
languageName: node
linkType: hard
+"@esbuild/freebsd-x64@npm:0.25.12":
+ version: 0.25.12
+ resolution: "@esbuild/freebsd-x64@npm:0.25.12"
+ conditions: os=freebsd & cpu=x64
+ languageName: node
+ linkType: hard
+
"@esbuild/freebsd-x64@npm:0.27.3":
version: 0.27.3
resolution: "@esbuild/freebsd-x64@npm:0.27.3"
@@ -1575,6 +1631,13 @@ __metadata:
languageName: node
linkType: hard
+"@esbuild/linux-arm64@npm:0.25.12":
+ version: 0.25.12
+ resolution: "@esbuild/linux-arm64@npm:0.25.12"
+ conditions: os=linux & cpu=arm64
+ languageName: node
+ linkType: hard
+
"@esbuild/linux-arm64@npm:0.27.3":
version: 0.27.3
resolution: "@esbuild/linux-arm64@npm:0.27.3"
@@ -1589,6 +1652,13 @@ __metadata:
languageName: node
linkType: hard
+"@esbuild/linux-arm@npm:0.25.12":
+ version: 0.25.12
+ resolution: "@esbuild/linux-arm@npm:0.25.12"
+ conditions: os=linux & cpu=arm
+ languageName: node
+ linkType: hard
+
"@esbuild/linux-arm@npm:0.27.3":
version: 0.27.3
resolution: "@esbuild/linux-arm@npm:0.27.3"
@@ -1603,6 +1673,13 @@ __metadata:
languageName: node
linkType: hard
+"@esbuild/linux-ia32@npm:0.25.12":
+ version: 0.25.12
+ resolution: "@esbuild/linux-ia32@npm:0.25.12"
+ conditions: os=linux & cpu=ia32
+ languageName: node
+ linkType: hard
+
"@esbuild/linux-ia32@npm:0.27.3":
version: 0.27.3
resolution: "@esbuild/linux-ia32@npm:0.27.3"
@@ -1617,6 +1694,13 @@ __metadata:
languageName: node
linkType: hard
+"@esbuild/linux-loong64@npm:0.25.12":
+ version: 0.25.12
+ resolution: "@esbuild/linux-loong64@npm:0.25.12"
+ conditions: os=linux & cpu=loong64
+ languageName: node
+ linkType: hard
+
"@esbuild/linux-loong64@npm:0.27.3":
version: 0.27.3
resolution: "@esbuild/linux-loong64@npm:0.27.3"
@@ -1631,6 +1715,13 @@ __metadata:
languageName: node
linkType: hard
+"@esbuild/linux-mips64el@npm:0.25.12":
+ version: 0.25.12
+ resolution: "@esbuild/linux-mips64el@npm:0.25.12"
+ conditions: os=linux & cpu=mips64el
+ languageName: node
+ linkType: hard
+
"@esbuild/linux-mips64el@npm:0.27.3":
version: 0.27.3
resolution: "@esbuild/linux-mips64el@npm:0.27.3"
@@ -1645,6 +1736,13 @@ __metadata:
languageName: node
linkType: hard
+"@esbuild/linux-ppc64@npm:0.25.12":
+ version: 0.25.12
+ resolution: "@esbuild/linux-ppc64@npm:0.25.12"
+ conditions: os=linux & cpu=ppc64
+ languageName: node
+ linkType: hard
+
"@esbuild/linux-ppc64@npm:0.27.3":
version: 0.27.3
resolution: "@esbuild/linux-ppc64@npm:0.27.3"
@@ -1659,6 +1757,13 @@ __metadata:
languageName: node
linkType: hard
+"@esbuild/linux-riscv64@npm:0.25.12":
+ version: 0.25.12
+ resolution: "@esbuild/linux-riscv64@npm:0.25.12"
+ conditions: os=linux & cpu=riscv64
+ languageName: node
+ linkType: hard
+
"@esbuild/linux-riscv64@npm:0.27.3":
version: 0.27.3
resolution: "@esbuild/linux-riscv64@npm:0.27.3"
@@ -1673,6 +1778,13 @@ __metadata:
languageName: node
linkType: hard
+"@esbuild/linux-s390x@npm:0.25.12":
+ version: 0.25.12
+ resolution: "@esbuild/linux-s390x@npm:0.25.12"
+ conditions: os=linux & cpu=s390x
+ languageName: node
+ linkType: hard
+
"@esbuild/linux-s390x@npm:0.27.3":
version: 0.27.3
resolution: "@esbuild/linux-s390x@npm:0.27.3"
@@ -1687,6 +1799,13 @@ __metadata:
languageName: node
linkType: hard
+"@esbuild/linux-x64@npm:0.25.12":
+ version: 0.25.12
+ resolution: "@esbuild/linux-x64@npm:0.25.12"
+ conditions: os=linux & cpu=x64
+ languageName: node
+ linkType: hard
+
"@esbuild/linux-x64@npm:0.27.3":
version: 0.27.3
resolution: "@esbuild/linux-x64@npm:0.27.3"
@@ -1694,6 +1813,13 @@ __metadata:
languageName: node
linkType: hard
+"@esbuild/netbsd-arm64@npm:0.25.12":
+ version: 0.25.12
+ resolution: "@esbuild/netbsd-arm64@npm:0.25.12"
+ conditions: os=netbsd & cpu=arm64
+ languageName: node
+ linkType: hard
+
"@esbuild/netbsd-arm64@npm:0.27.3":
version: 0.27.3
resolution: "@esbuild/netbsd-arm64@npm:0.27.3"
@@ -1708,6 +1834,13 @@ __metadata:
languageName: node
linkType: hard
+"@esbuild/netbsd-x64@npm:0.25.12":
+ version: 0.25.12
+ resolution: "@esbuild/netbsd-x64@npm:0.25.12"
+ conditions: os=netbsd & cpu=x64
+ languageName: node
+ linkType: hard
+
"@esbuild/netbsd-x64@npm:0.27.3":
version: 0.27.3
resolution: "@esbuild/netbsd-x64@npm:0.27.3"
@@ -1715,6 +1848,13 @@ __metadata:
languageName: node
linkType: hard
+"@esbuild/openbsd-arm64@npm:0.25.12":
+ version: 0.25.12
+ resolution: "@esbuild/openbsd-arm64@npm:0.25.12"
+ conditions: os=openbsd & cpu=arm64
+ languageName: node
+ linkType: hard
+
"@esbuild/openbsd-arm64@npm:0.27.3":
version: 0.27.3
resolution: "@esbuild/openbsd-arm64@npm:0.27.3"
@@ -1729,6 +1869,13 @@ __metadata:
languageName: node
linkType: hard
+"@esbuild/openbsd-x64@npm:0.25.12":
+ version: 0.25.12
+ resolution: "@esbuild/openbsd-x64@npm:0.25.12"
+ conditions: os=openbsd & cpu=x64
+ languageName: node
+ linkType: hard
+
"@esbuild/openbsd-x64@npm:0.27.3":
version: 0.27.3
resolution: "@esbuild/openbsd-x64@npm:0.27.3"
@@ -1736,6 +1883,13 @@ __metadata:
languageName: node
linkType: hard
+"@esbuild/openharmony-arm64@npm:0.25.12":
+ version: 0.25.12
+ resolution: "@esbuild/openharmony-arm64@npm:0.25.12"
+ conditions: os=openharmony & cpu=arm64
+ languageName: node
+ linkType: hard
+
"@esbuild/openharmony-arm64@npm:0.27.3":
version: 0.27.3
resolution: "@esbuild/openharmony-arm64@npm:0.27.3"
@@ -1750,6 +1904,13 @@ __metadata:
languageName: node
linkType: hard
+"@esbuild/sunos-x64@npm:0.25.12":
+ version: 0.25.12
+ resolution: "@esbuild/sunos-x64@npm:0.25.12"
+ conditions: os=sunos & cpu=x64
+ languageName: node
+ linkType: hard
+
"@esbuild/sunos-x64@npm:0.27.3":
version: 0.27.3
resolution: "@esbuild/sunos-x64@npm:0.27.3"
@@ -1764,6 +1925,13 @@ __metadata:
languageName: node
linkType: hard
+"@esbuild/win32-arm64@npm:0.25.12":
+ version: 0.25.12
+ resolution: "@esbuild/win32-arm64@npm:0.25.12"
+ conditions: os=win32 & cpu=arm64
+ languageName: node
+ linkType: hard
+
"@esbuild/win32-arm64@npm:0.27.3":
version: 0.27.3
resolution: "@esbuild/win32-arm64@npm:0.27.3"
@@ -1778,6 +1946,13 @@ __metadata:
languageName: node
linkType: hard
+"@esbuild/win32-ia32@npm:0.25.12":
+ version: 0.25.12
+ resolution: "@esbuild/win32-ia32@npm:0.25.12"
+ conditions: os=win32 & cpu=ia32
+ languageName: node
+ linkType: hard
+
"@esbuild/win32-ia32@npm:0.27.3":
version: 0.27.3
resolution: "@esbuild/win32-ia32@npm:0.27.3"
@@ -1792,6 +1967,13 @@ __metadata:
languageName: node
linkType: hard
+"@esbuild/win32-x64@npm:0.25.12":
+ version: 0.25.12
+ resolution: "@esbuild/win32-x64@npm:0.25.12"
+ conditions: os=win32 & cpu=x64
+ languageName: node
+ linkType: hard
+
"@esbuild/win32-x64@npm:0.27.3":
version: 0.27.3
resolution: "@esbuild/win32-x64@npm:0.27.3"
@@ -3193,6 +3375,40 @@ __metadata:
languageName: node
linkType: hard
+"@playwright/experimental-ct-core@npm:1.58.2":
+ version: 1.58.2
+ resolution: "@playwright/experimental-ct-core@npm:1.58.2"
+ dependencies:
+ playwright: "npm:1.58.2"
+ playwright-core: "npm:1.58.2"
+ vite: "npm:^6.4.1"
+ checksum: 10c0/cb0b3a851290027f68da62fcd1ae5408db472e7bae2a43c453fcaefb73d818927a2010103546b52e3ebaa15eb3b395f768a48fd7eae7d2277cfd8ce54e1d67dc
+ languageName: node
+ linkType: hard
+
+"@playwright/experimental-ct-react@npm:1.58.2":
+ version: 1.58.2
+ resolution: "@playwright/experimental-ct-react@npm:1.58.2"
+ dependencies:
+ "@playwright/experimental-ct-core": "npm:1.58.2"
+ "@vitejs/plugin-react": "npm:^4.2.1"
+ bin:
+ playwright: cli.js
+ checksum: 10c0/e887ee411c77a8a974626c1389afc2d17d617fa6e9e93bbfd07c5622705f9fec587d26b401db50e0fc7d9cb9adf593195cb54f97974eae11407d7d135021b916
+ languageName: node
+ linkType: hard
+
+"@playwright/test@npm:1.58.2":
+ version: 1.58.2
+ resolution: "@playwright/test@npm:1.58.2"
+ dependencies:
+ playwright: "npm:1.58.2"
+ bin:
+ playwright: cli.js
+ checksum: 10c0/2164c03ad97c3653ff02e8818a71f3b2bbc344ac07924c9d8e31cd57505d6d37596015a41f51396b3ed8de6840f59143eaa9c21bf65515963da20740119811da
+ languageName: node
+ linkType: hard
+
"@prettier/sync@npm:^0.5.2":
version: 0.5.5
resolution: "@prettier/sync@npm:0.5.5"
@@ -3204,6 +3420,13 @@ __metadata:
languageName: node
linkType: hard
+"@rolldown/pluginutils@npm:1.0.0-beta.27":
+ version: 1.0.0-beta.27
+ resolution: "@rolldown/pluginutils@npm:1.0.0-beta.27"
+ checksum: 10c0/9658f235b345201d4f6bfb1f32da9754ca164f892d1cb68154fe5f53c1df42bd675ecd409836dff46884a7847d6c00bdc38af870f7c81e05bba5c2645eb4ab9c
+ languageName: node
+ linkType: hard
+
"@rolldown/pluginutils@npm:1.0.0-rc.3":
version: 1.0.0-rc.3
resolution: "@rolldown/pluginutils@npm:1.0.0-rc.3"
@@ -5420,6 +5643,22 @@ __metadata:
languageName: node
linkType: hard
+"@vitejs/plugin-react@npm:^4.2.1":
+ version: 4.7.0
+ resolution: "@vitejs/plugin-react@npm:4.7.0"
+ dependencies:
+ "@babel/core": "npm:^7.28.0"
+ "@babel/plugin-transform-react-jsx-self": "npm:^7.27.1"
+ "@babel/plugin-transform-react-jsx-source": "npm:^7.27.1"
+ "@rolldown/pluginutils": "npm:1.0.0-beta.27"
+ "@types/babel__core": "npm:^7.20.5"
+ react-refresh: "npm:^0.17.0"
+ peerDependencies:
+ vite: ^4.2.0 || ^5.0.0 || ^6.0.0 || ^7.0.0
+ checksum: 10c0/692f23960972879485d647713663ec299c478222c96567d60285acf7c7dc5c178e71abfe9d2eefddef1eeb01514dacbc2ed68aad84628debf9c7116134734253
+ languageName: node
+ linkType: hard
+
"@vitest/expect@npm:3.2.4":
version: 3.2.4
resolution: "@vitest/expect@npm:3.2.4"
@@ -9344,6 +9583,95 @@ __metadata:
languageName: node
linkType: hard
+"esbuild@npm:^0.25.0":
+ version: 0.25.12
+ resolution: "esbuild@npm:0.25.12"
+ dependencies:
+ "@esbuild/aix-ppc64": "npm:0.25.12"
+ "@esbuild/android-arm": "npm:0.25.12"
+ "@esbuild/android-arm64": "npm:0.25.12"
+ "@esbuild/android-x64": "npm:0.25.12"
+ "@esbuild/darwin-arm64": "npm:0.25.12"
+ "@esbuild/darwin-x64": "npm:0.25.12"
+ "@esbuild/freebsd-arm64": "npm:0.25.12"
+ "@esbuild/freebsd-x64": "npm:0.25.12"
+ "@esbuild/linux-arm": "npm:0.25.12"
+ "@esbuild/linux-arm64": "npm:0.25.12"
+ "@esbuild/linux-ia32": "npm:0.25.12"
+ "@esbuild/linux-loong64": "npm:0.25.12"
+ "@esbuild/linux-mips64el": "npm:0.25.12"
+ "@esbuild/linux-ppc64": "npm:0.25.12"
+ "@esbuild/linux-riscv64": "npm:0.25.12"
+ "@esbuild/linux-s390x": "npm:0.25.12"
+ "@esbuild/linux-x64": "npm:0.25.12"
+ "@esbuild/netbsd-arm64": "npm:0.25.12"
+ "@esbuild/netbsd-x64": "npm:0.25.12"
+ "@esbuild/openbsd-arm64": "npm:0.25.12"
+ "@esbuild/openbsd-x64": "npm:0.25.12"
+ "@esbuild/openharmony-arm64": "npm:0.25.12"
+ "@esbuild/sunos-x64": "npm:0.25.12"
+ "@esbuild/win32-arm64": "npm:0.25.12"
+ "@esbuild/win32-ia32": "npm:0.25.12"
+ "@esbuild/win32-x64": "npm:0.25.12"
+ dependenciesMeta:
+ "@esbuild/aix-ppc64":
+ optional: true
+ "@esbuild/android-arm":
+ optional: true
+ "@esbuild/android-arm64":
+ optional: true
+ "@esbuild/android-x64":
+ optional: true
+ "@esbuild/darwin-arm64":
+ optional: true
+ "@esbuild/darwin-x64":
+ optional: true
+ "@esbuild/freebsd-arm64":
+ optional: true
+ "@esbuild/freebsd-x64":
+ optional: true
+ "@esbuild/linux-arm":
+ optional: true
+ "@esbuild/linux-arm64":
+ optional: true
+ "@esbuild/linux-ia32":
+ optional: true
+ "@esbuild/linux-loong64":
+ optional: true
+ "@esbuild/linux-mips64el":
+ optional: true
+ "@esbuild/linux-ppc64":
+ optional: true
+ "@esbuild/linux-riscv64":
+ optional: true
+ "@esbuild/linux-s390x":
+ optional: true
+ "@esbuild/linux-x64":
+ optional: true
+ "@esbuild/netbsd-arm64":
+ optional: true
+ "@esbuild/netbsd-x64":
+ optional: true
+ "@esbuild/openbsd-arm64":
+ optional: true
+ "@esbuild/openbsd-x64":
+ optional: true
+ "@esbuild/openharmony-arm64":
+ optional: true
+ "@esbuild/sunos-x64":
+ optional: true
+ "@esbuild/win32-arm64":
+ optional: true
+ "@esbuild/win32-ia32":
+ optional: true
+ "@esbuild/win32-x64":
+ optional: true
+ bin:
+ esbuild: bin/esbuild
+ checksum: 10c0/c205357531423220a9de8e1e6c6514242bc9b1666e762cd67ccdf8fdfdc3f1d0bd76f8d9383958b97ad4c953efdb7b6e8c1f9ca5951cd2b7c5235e8755b34a6b
+ languageName: node
+ linkType: hard
+
"escalade@npm:^3.1.1, escalade@npm:^3.2.0":
version: 3.2.0
resolution: "escalade@npm:3.2.0"
@@ -10154,7 +10482,7 @@ __metadata:
languageName: node
linkType: hard
-"fdir@npm:^6.4.3, fdir@npm:^6.5.0":
+"fdir@npm:^6.4.3, fdir@npm:^6.4.4, fdir@npm:^6.5.0":
version: 6.5.0
resolution: "fdir@npm:6.5.0"
peerDependencies:
@@ -10503,6 +10831,16 @@ __metadata:
languageName: node
linkType: hard
+"fsevents@npm:2.3.2":
+ version: 2.3.2
+ resolution: "fsevents@npm:2.3.2"
+ dependencies:
+ node-gyp: "npm:latest"
+ checksum: 10c0/be78a3efa3e181cda3cf7a4637cb527bcebb0bd0ea0440105a3bb45b86f9245b307dc10a2507e8f4498a7d4ec349d1910f4d73e4d4495b16103106e07eee735b
+ conditions: os=darwin
+ languageName: node
+ linkType: hard
+
"fsevents@npm:~2.3.2, fsevents@npm:~2.3.3":
version: 2.3.3
resolution: "fsevents@npm:2.3.3"
@@ -10513,6 +10851,15 @@ __metadata:
languageName: node
linkType: hard
+"fsevents@patch:fsevents@npm%3A2.3.2#optional!builtin":
+ version: 2.3.2
+ resolution: "fsevents@patch:fsevents@npm%3A2.3.2#optional!builtin::version=2.3.2&hash=df0bf1"
+ dependencies:
+ node-gyp: "npm:latest"
+ conditions: os=darwin
+ languageName: node
+ linkType: hard
+
"fsevents@patch:fsevents@npm%3A~2.3.2#optional!builtin, fsevents@patch:fsevents@npm%3A~2.3.3#optional!builtin":
version: 2.3.3
resolution: "fsevents@patch:fsevents@npm%3A2.3.3#optional!builtin::version=2.3.3&hash=df0bf1"
@@ -16487,6 +16834,30 @@ __metadata:
languageName: node
linkType: hard
+"playwright-core@npm:1.58.2":
+ version: 1.58.2
+ resolution: "playwright-core@npm:1.58.2"
+ bin:
+ playwright-core: cli.js
+ checksum: 10c0/5aa15b2b764e6ffe738293a09081a6f7023847a0dbf4cd05fe10eed2e25450d321baf7482f938f2d2eb330291e197fa23e57b29a5b552b89927ceb791266225b
+ languageName: node
+ linkType: hard
+
+"playwright@npm:1.58.2":
+ version: 1.58.2
+ resolution: "playwright@npm:1.58.2"
+ dependencies:
+ fsevents: "npm:2.3.2"
+ playwright-core: "npm:1.58.2"
+ dependenciesMeta:
+ fsevents:
+ optional: true
+ bin:
+ playwright: cli.js
+ checksum: 10c0/d060d9b7cc124bd8b5dffebaab5e84f6b34654a553758fe7b19cc598dfbee93f6ecfbdc1832b40a6380ae04eade86ef3285ba03aa0b136799e83402246dc0727
+ languageName: node
+ linkType: hard
+
"possible-typed-array-names@npm:^1.0.0":
version: 1.1.0
resolution: "possible-typed-array-names@npm:1.1.0"
@@ -17014,7 +17385,7 @@ __metadata:
languageName: node
linkType: hard
-"postcss@npm:8.5.6, postcss@npm:^8.4.43, postcss@npm:^8.4.5, postcss@npm:^8.5.6":
+"postcss@npm:8.5.6, postcss@npm:^8.4.43, postcss@npm:^8.4.5, postcss@npm:^8.5.3, postcss@npm:^8.5.6":
version: 8.5.6
resolution: "postcss@npm:8.5.6"
dependencies:
@@ -17460,6 +17831,13 @@ __metadata:
languageName: node
linkType: hard
+"react-refresh@npm:^0.17.0":
+ version: 0.17.0
+ resolution: "react-refresh@npm:0.17.0"
+ checksum: 10c0/002cba940384c9930008c0bce26cac97a9d5682bc623112c2268ba0c155127d9c178a9a5cc2212d560088d60dfd503edd808669a25f9b377f316a32361d0b23c
+ languageName: node
+ linkType: hard
+
"react-refresh@npm:^0.18.0":
version: 0.18.0
resolution: "react-refresh@npm:0.18.0"
@@ -18173,7 +18551,7 @@ __metadata:
languageName: node
linkType: hard
-"rollup@npm:^4.20.0, rollup@npm:^4.43.0":
+"rollup@npm:^4.20.0, rollup@npm:^4.34.9, rollup@npm:^4.43.0":
version: 4.57.1
resolution: "rollup@npm:4.57.1"
dependencies:
@@ -19487,7 +19865,7 @@ __metadata:
languageName: node
linkType: hard
-"tinyglobby@npm:^0.2.12, tinyglobby@npm:^0.2.14, tinyglobby@npm:^0.2.15":
+"tinyglobby@npm:^0.2.12, tinyglobby@npm:^0.2.13, tinyglobby@npm:^0.2.14, tinyglobby@npm:^0.2.15":
version: 0.2.15
resolution: "tinyglobby@npm:0.2.15"
dependencies:
@@ -20005,6 +20383,8 @@ __metadata:
"@cypress/code-coverage": "npm:4.0.0"
"@eslint/compat": "npm:2.0.2"
"@eslint/js": "npm:9.39.3"
+ "@playwright/experimental-ct-react": "npm:1.58.2"
+ "@playwright/test": "npm:1.58.2"
"@semantic-release/github": "npm:12.0.6"
"@stackblitz/sdk": "npm:1.11.0"
"@storybook/addon-a11y": "npm:10.2.10"
@@ -20821,6 +21201,61 @@ __metadata:
languageName: node
linkType: hard
+"vite@npm:^6.4.1":
+ version: 6.4.1
+ resolution: "vite@npm:6.4.1"
+ dependencies:
+ esbuild: "npm:^0.25.0"
+ fdir: "npm:^6.4.4"
+ fsevents: "npm:~2.3.3"
+ picomatch: "npm:^4.0.2"
+ postcss: "npm:^8.5.3"
+ rollup: "npm:^4.34.9"
+ tinyglobby: "npm:^0.2.13"
+ peerDependencies:
+ "@types/node": ^18.0.0 || ^20.0.0 || >=22.0.0
+ jiti: ">=1.21.0"
+ less: "*"
+ lightningcss: ^1.21.0
+ sass: "*"
+ sass-embedded: "*"
+ stylus: "*"
+ sugarss: "*"
+ terser: ^5.16.0
+ tsx: ^4.8.1
+ yaml: ^2.4.2
+ dependenciesMeta:
+ fsevents:
+ optional: true
+ peerDependenciesMeta:
+ "@types/node":
+ optional: true
+ jiti:
+ optional: true
+ less:
+ optional: true
+ lightningcss:
+ optional: true
+ sass:
+ optional: true
+ sass-embedded:
+ optional: true
+ stylus:
+ optional: true
+ sugarss:
+ optional: true
+ terser:
+ optional: true
+ tsx:
+ optional: true
+ yaml:
+ optional: true
+ bin:
+ vite: bin/vite.js
+ checksum: 10c0/77bb4c5b10f2a185e7859cc9a81c789021bc18009b02900347d1583b453b58e4b19ff07a5e5a5b522b68fc88728460bb45a63b104d969e8c6a6152aea3b849f7
+ languageName: node
+ linkType: hard
+
"vue-template-compiler@npm:^2.7.8":
version: 2.7.16
resolution: "vue-template-compiler@npm:2.7.16"