` first in the DOM, so scope to the
+// container that actually holds a combobox — the pre-migration
+// `.rule--field .ant-select` selector filtered these out implicitly.
+const comboboxField = (scope: Page | Locator, className: string): Locator =>
+ scope
+ .locator(className)
+ .filter({ has: scope.locator('input[role="combobox"]') });
+
// Reusable rule fixture for tests that only need a visible rule card to exist
// (cache-state tests, edit-discard test). Keeps the inline mock objects DRY.
const PROBE_RULE: ContextRule = {
@@ -547,9 +557,13 @@ test.describe.serial('Persona AI Context', () => {
}
await adminPage.getByTestId('add-context-condition').click();
+ // Conjunction toggle is a react-aria ToggleButtonGroup with
+ // selectionMode="single", which renders a radiogroup whose items expose
+ // role="radio" (not button) — matching how CuratedAssets and the advanced
+ // search helper query the same toggle.
const orOperator = adminPage
.getByRole('dialog')
- .getByRole('button', { name: 'Or', exact: true });
+ .getByRole('radio', { name: 'Or', exact: true });
await expect(orOperator).toBeVisible();
await orOperator.click();
await expect(adminPage.getByTestId('delete-condition-button')).toHaveCount(
@@ -1297,28 +1311,23 @@ test.describe.serial('Persona AI Context', () => {
await adminPage.getByTestId('empty-add-context-rule').click();
await adminPage.getByTestId('add-context-condition').click();
- await adminPage
- .locator('.rule--field .ant-select')
- .first()
- .waitFor({ state: 'visible' });
- await selectOption(
- adminPage,
- adminPage.locator('.rule--field .ant-select').first(),
- 'Custom Properties',
- true
- );
+ const fieldContainer = comboboxField(adminPage, '.rule--field').first();
+ await fieldContainer.waitFor({ state: 'visible' });
+ await selectOption(adminPage, fieldContainer, 'Custom Properties', true);
- // The sub-field selector must appear — click it and verify our mocked property
- // is listed and "No data" is absent.
- const subFieldSelect = adminPage.locator('.rule--field .ant-select').last();
- await subFieldSelect.click();
- const dropdown = adminPage.locator('.ant-select-dropdown:visible').first();
- await dropdown.waitFor({ state: 'visible' });
- await expect(dropdown).toContainText('pw-context-enum-prop');
- await expect(dropdown).not.toContainText('No data');
+ const fieldInput = fieldContainer.locator('input[role="combobox"]');
+ await fieldInput.fill('');
+ await fieldInput.press('ArrowDown');
- await adminPage.keyboard.press('Escape');
+ await expect(
+ adminPage.getByRole('option', { name: 'pw-context-enum-prop' })
+ ).toBeVisible();
+ await expect(
+ adminPage.getByRole('option', { name: 'No data' })
+ ).toHaveCount(0);
+
+ await fieldInput.blur();
});
// Regression guard for the hasUnfinishedRule bug exercising the async-dropdown
@@ -1367,25 +1376,16 @@ test.describe.serial('Persona AI Context', () => {
.fill('service-is-regression-test');
await adminPage.getByTestId('add-context-condition').click();
- await adminPage
- .locator('.rule--field .ant-select')
- .first()
- .waitFor({ state: 'visible' });
+ const serviceField = comboboxField(adminPage, '.rule--field').first();
+ await serviceField.waitFor({ state: 'visible' });
- await selectOption(
- adminPage,
- adminPage.locator('.rule--field .ant-select').first(),
- 'Service',
- true
- );
+ await selectOption(adminPage, serviceField, 'Service', true);
- const operatorLocator = adminPage
- .locator('.rule--operator .ant-select')
- .first();
+ const operatorLocator = comboboxField(adminPage, '.rule--operator').first();
await operatorLocator.waitFor({ state: 'visible', timeout: 5000 });
await selectOption(adminPage, operatorLocator, 'Is', false);
- const valueSelect = adminPage.locator('.rule--widget .ant-select').first();
+ const valueSelect = comboboxField(adminPage, '.rule--widget').first();
await valueSelect.waitFor({ state: 'visible' });
await selectOption(adminPage, valueSelect, dbService.entity.name, true);
@@ -1661,11 +1661,11 @@ test.describe.serial('Persona AI Context', () => {
adminPage.getByTestId('delete-condition-button')
).toHaveCount(2);
- const firstField = drawer.locator('.rule--field .ant-select').first();
+ const firstField = comboboxField(adminPage, '.rule--field').first();
await firstField.waitFor({ state: 'visible' });
await selectOption(adminPage, firstField, 'Description', true);
- const firstOp = drawer.locator('.rule--operator .ant-select').first();
+ const firstOp = comboboxField(adminPage, '.rule--operator').first();
await firstOp.waitFor({ state: 'visible', timeout: 5000 });
await selectOption(adminPage, firstOp, 'Contains', false);
const alphaInput = drawer
@@ -1691,10 +1691,10 @@ test.describe.serial('Persona AI Context', () => {
adminPage.getByTestId('delete-condition-button')
).toHaveCount(3);
- const secondField = drawer.locator('.rule--field .ant-select').last();
+ const secondField = comboboxField(adminPage, '.rule--field').last();
await selectOption(adminPage, secondField, 'Description', true);
- const secondOp = drawer.locator('.rule--operator .ant-select').last();
+ const secondOp = comboboxField(adminPage, '.rule--operator').last();
await secondOp.waitFor({ state: 'visible', timeout: 5000 });
await selectOption(adminPage, secondOp, 'Contains', false);
const betaInput = drawer
@@ -1707,7 +1707,7 @@ test.describe.serial('Persona AI Context', () => {
// Only now change the root conjunction to OR — this just flips the
// conjunction on the existing two-rule group without any structural
// change, so both alpha and beta remain in the serialized query.
- await drawer.getByRole('button', { name: 'Or', exact: true }).click();
+ await drawer.getByRole('radio', { name: 'Or', exact: true }).click();
});
const createRuleRequest = adminPage.waitForRequest(
diff --git a/openmetadata-ui/src/main/resources/ui/playwright/e2e/Flow/PersonaFlow.spec.ts b/openmetadata-ui/src/main/resources/ui/playwright/e2e/Flow/PersonaFlow.spec.ts
index a4e1415c2975..1208a31a9eaa 100644
--- a/openmetadata-ui/src/main/resources/ui/playwright/e2e/Flow/PersonaFlow.spec.ts
+++ b/openmetadata-ui/src/main/resources/ui/playwright/e2e/Flow/PersonaFlow.spec.ts
@@ -850,13 +850,13 @@ test.describe('Curated Assets – Description filter', () => {
await selectOption(
adminPage,
- rule0.locator('.rule--field .ant-select'),
+ rule0.locator('.rule--field'),
'Description',
true
);
await selectOption(
adminPage,
- rule0.locator('.rule--operator .ant-select'),
+ rule0.locator('.rule--operator'),
'Contains'
);
await rule0
diff --git a/openmetadata-ui/src/main/resources/ui/playwright/e2e/Pages/CustomProperties.spec.ts b/openmetadata-ui/src/main/resources/ui/playwright/e2e/Pages/CustomProperties.spec.ts
index d7f915cb24a1..8ef643cc4a2b 100644
--- a/openmetadata-ui/src/main/resources/ui/playwright/e2e/Pages/CustomProperties.spec.ts
+++ b/openmetadata-ui/src/main/resources/ui/playwright/e2e/Pages/CustomProperties.spec.ts
@@ -943,26 +943,26 @@ ALL_ENTITIES.forEach(({ key, makeInstance }) => {
await selectOption(
page,
- ruleLocator.locator('.rule--field .ant-select'),
+ ruleLocator.locator('.rule--field'),
'Custom Properties',
true
);
await selectOption(
page,
- ruleLocator.locator('.rule--field .ant-select'),
+ ruleLocator.locator('.rule--field'),
'Table',
true
);
await selectOption(
page,
- ruleLocator.locator('.rule--field .ant-select'),
+ ruleLocator.locator('.rule--field'),
durationPropertyName,
true
);
await selectOption(
page,
- ruleLocator.locator('.rule--operator .ant-select'),
+ ruleLocator.locator('.rule--operator'),
CONDITIONS_MUST.equalTo.name
);
@@ -985,7 +985,7 @@ ALL_ENTITIES.forEach(({ key, makeInstance }) => {
await selectOption(
page,
- ruleLocator.locator('.rule--operator .ant-select'),
+ ruleLocator.locator('.rule--operator'),
'Contains'
);
await inputElement.fill(partialSearchValue);
@@ -1237,28 +1237,28 @@ ALL_ENTITIES.forEach(({ key, makeInstance }) => {
await selectOption(
page,
- ruleLocator.locator('.rule--field .ant-select'),
+ ruleLocator.locator('.rule--field'),
'Custom Properties',
true
);
await selectOption(
page,
- ruleLocator.locator('.rule--field .ant-select'),
+ ruleLocator.locator('.rule--field'),
'Table',
true
);
await selectOption(
page,
- ruleLocator.locator('.rule--field .ant-select'),
+ ruleLocator.locator('.rule--field'),
propertyName,
true
);
await selectOption(
page,
- ruleLocator.locator('.rule--operator .ant-select'),
+ ruleLocator.locator('.rule--operator'),
CONDITIONS_MUST.equalTo.name
);
diff --git a/openmetadata-ui/src/main/resources/ui/playwright/e2e/Pages/DataContractInheritance.spec.ts b/openmetadata-ui/src/main/resources/ui/playwright/e2e/Pages/DataContractInheritance.spec.ts
index e2923800943a..796ff9a84c62 100644
--- a/openmetadata-ui/src/main/resources/ui/playwright/e2e/Pages/DataContractInheritance.spec.ts
+++ b/openmetadata-ui/src/main/resources/ui/playwright/e2e/Pages/DataContractInheritance.spec.ts
@@ -101,13 +101,13 @@ const fillSemanticsForm = async (
const ruleLocator = page.locator('.group').nth(0);
await selectOption(
page,
- ruleLocator.locator('.group--field .ant-select'),
+ ruleLocator.locator('.group--field'),
semanticsData.rules[0].field,
true
);
await selectOption(
page,
- ruleLocator.locator('.rule--operator .ant-select'),
+ ruleLocator.locator('.rule--operator'),
semanticsData.rules[0].operator
);
diff --git a/openmetadata-ui/src/main/resources/ui/playwright/e2e/Pages/DataContracts.spec.ts b/openmetadata-ui/src/main/resources/ui/playwright/e2e/Pages/DataContracts.spec.ts
index 0347c083f812..9cacf8016524 100644
--- a/openmetadata-ui/src/main/resources/ui/playwright/e2e/Pages/DataContracts.spec.ts
+++ b/openmetadata-ui/src/main/resources/ui/playwright/e2e/Pages/DataContracts.spec.ts
@@ -250,18 +250,18 @@ test.describe('Data Contracts', () => {
const ruleLocator = page.locator('.group').nth(0);
await selectOption(
page,
- ruleLocator.locator('.group--field .ant-select'),
+ ruleLocator.locator('.group--field'),
DATA_CONTRACT_SEMANTICS1.rules[0].field,
true
);
await selectOption(
page,
- ruleLocator.locator('.rule--operator .ant-select'),
+ ruleLocator.locator('.rule--operator'),
DATA_CONTRACT_SEMANTICS1.rules[0].operator
);
await selectOption(
page,
- ruleLocator.locator('.rule--value .ant-select'),
+ ruleLocator.locator('.rule--value'),
user.getUserDisplayName(),
true
);
@@ -272,13 +272,13 @@ test.describe('Data Contracts', () => {
const ruleLocator2 = page.locator('.rule').nth(1);
await selectOption(
page,
- ruleLocator2.locator('.rule--field .ant-select'),
+ ruleLocator2.locator('.rule--field'),
DATA_CONTRACT_SEMANTICS1.rules[1].field,
true
);
await selectOption(
page,
- ruleLocator2.locator('.rule--operator .ant-select'),
+ ruleLocator2.locator('.rule--operator'),
DATA_CONTRACT_SEMANTICS1.rules[1].operator
);
await page.getByTestId('save-semantic-button').click();
@@ -311,13 +311,13 @@ test.describe('Data Contracts', () => {
const ruleLocator3 = page.locator('.group').nth(2);
await selectOption(
page,
- ruleLocator3.locator('.group--field .ant-select'),
+ ruleLocator3.locator('.group--field'),
DATA_CONTRACT_SEMANTICS2.rules[0].field,
true
);
await selectOption(
page,
- ruleLocator3.locator('.rule--operator .ant-select'),
+ ruleLocator3.locator('.rule--operator'),
DATA_CONTRACT_SEMANTICS2.rules[0].operator
);
await page.getByTestId('save-semantic-button').click();
@@ -1024,18 +1024,18 @@ test.describe('Data Contracts', () => {
const ruleLocator = page.locator('.group').nth(0);
await selectOption(
page,
- ruleLocator.locator('.group--field .ant-select'),
+ ruleLocator.locator('.group--field'),
DATA_CONTRACT_CONTAIN_SEMANTICS.rules[0].field,
true
);
await selectOption(
page,
- ruleLocator.locator('.rule--operator .ant-select'),
+ ruleLocator.locator('.rule--operator'),
DATA_CONTRACT_CONTAIN_SEMANTICS.rules[0].operator
);
await selectOption(
page,
- ruleLocator.locator('.rule--value .ant-select'),
+ ruleLocator.locator('.rule--value'),
'Tier.Tier1',
true
);
@@ -1046,19 +1046,19 @@ test.describe('Data Contracts', () => {
const ruleLocator2 = page.locator('.rule').nth(1);
await selectOption(
page,
- ruleLocator2.locator('.rule--field .ant-select'),
+ ruleLocator2.locator('.rule--field'),
DATA_CONTRACT_CONTAIN_SEMANTICS.rules[1].field,
true
);
await selectOption(
page,
- ruleLocator2.locator('.rule--operator .ant-select'),
+ ruleLocator2.locator('.rule--operator'),
DATA_CONTRACT_CONTAIN_SEMANTICS.rules[1].operator
);
await selectOption(
page,
- ruleLocator2.locator('.rule--value .ant-select'),
+ ruleLocator2.locator('.rule--value'),
testTag.responseData.name,
true
);
@@ -1072,19 +1072,19 @@ test.describe('Data Contracts', () => {
const ruleLocator3 = page.locator('.rule').nth(2);
await selectOption(
page,
- ruleLocator3.locator('.rule--field .ant-select'),
+ ruleLocator3.locator('.rule--field'),
DATA_CONTRACT_CONTAIN_SEMANTICS.rules[2].field,
true
);
await selectOption(
page,
- ruleLocator3.locator('.rule--operator .ant-select'),
+ ruleLocator3.locator('.rule--operator'),
DATA_CONTRACT_CONTAIN_SEMANTICS.rules[2].operator
);
await selectOption(
page,
- ruleLocator3.locator('.rule--value .ant-select'),
+ ruleLocator3.locator('.rule--value'),
testGlossaryTerm.responseData.name,
true
);
@@ -1217,18 +1217,18 @@ test.describe('Data Contracts', () => {
const ruleLocator = page.locator('.group').nth(0);
await selectOption(
page,
- ruleLocator.locator('.group--field .ant-select'),
+ ruleLocator.locator('.group--field'),
DATA_CONTRACT_NOT_CONTAIN_SEMANTICS.rules[0].field,
true
);
await selectOption(
page,
- ruleLocator.locator('.rule--operator .ant-select'),
+ ruleLocator.locator('.rule--operator'),
DATA_CONTRACT_NOT_CONTAIN_SEMANTICS.rules[0].operator
);
await selectOption(
page,
- ruleLocator.locator('.rule--value .ant-select'),
+ ruleLocator.locator('.rule--value'),
'Tier.Tier1',
true
);
@@ -1239,19 +1239,19 @@ test.describe('Data Contracts', () => {
const ruleLocator2 = page.locator('.rule').nth(1);
await selectOption(
page,
- ruleLocator2.locator('.rule--field .ant-select'),
+ ruleLocator2.locator('.rule--field'),
DATA_CONTRACT_NOT_CONTAIN_SEMANTICS.rules[1].field,
true
);
await selectOption(
page,
- ruleLocator2.locator('.rule--operator .ant-select'),
+ ruleLocator2.locator('.rule--operator'),
DATA_CONTRACT_NOT_CONTAIN_SEMANTICS.rules[1].operator
);
await selectOption(
page,
- ruleLocator2.locator('.rule--value .ant-select'),
+ ruleLocator2.locator('.rule--value'),
testTag.responseData.name,
true
);
@@ -1265,19 +1265,19 @@ test.describe('Data Contracts', () => {
const ruleLocator3 = page.locator('.rule').nth(2);
await selectOption(
page,
- ruleLocator3.locator('.rule--field .ant-select'),
+ ruleLocator3.locator('.rule--field'),
DATA_CONTRACT_NOT_CONTAIN_SEMANTICS.rules[2].field,
true
);
await selectOption(
page,
- ruleLocator3.locator('.rule--operator .ant-select'),
+ ruleLocator3.locator('.rule--operator'),
DATA_CONTRACT_NOT_CONTAIN_SEMANTICS.rules[2].operator
);
await selectOption(
page,
- ruleLocator3.locator('.rule--value .ant-select'),
+ ruleLocator3.locator('.rule--value'),
testGlossaryTerm.responseData.name,
true
);
@@ -1598,18 +1598,18 @@ test.describe('Data Contracts', () => {
const ruleLocator = page.locator('.group').nth(0);
await selectOption(
page,
- ruleLocator.locator('.group--field .ant-select'),
+ ruleLocator.locator('.group--field'),
DATA_CONTRACT_SEMANTICS1.rules[0].field,
true
);
await selectOption(
page,
- ruleLocator.locator('.rule--operator .ant-select'),
+ ruleLocator.locator('.rule--operator'),
DATA_CONTRACT_SEMANTICS1.rules[0].operator
);
await selectOption(
page,
- ruleLocator.locator('.rule--value .ant-select'),
+ ruleLocator.locator('.rule--value'),
'admin',
true
);
@@ -1620,13 +1620,13 @@ test.describe('Data Contracts', () => {
const ruleLocator2 = page.locator('.rule').nth(1);
await selectOption(
page,
- ruleLocator2.locator('.rule--field .ant-select'),
+ ruleLocator2.locator('.rule--field'),
DATA_CONTRACT_SEMANTICS1.rules[1].field,
true
);
await selectOption(
page,
- ruleLocator2.locator('.rule--operator .ant-select'),
+ ruleLocator2.locator('.rule--operator'),
DATA_CONTRACT_SEMANTICS1.rules[1].operator
);
await page.getByTestId('save-semantic-button').click();
@@ -1678,18 +1678,18 @@ test.describe('Data Contracts', () => {
const ruleLocator = page.locator('.group').nth(0);
await selectOption(
page,
- ruleLocator.locator('.group--field .ant-select'),
+ ruleLocator.locator('.group--field'),
DATA_CONTRACT_SEMANTICS1.rules[0].field,
true
);
await selectOption(
page,
- ruleLocator.locator('.rule--operator .ant-select'),
+ ruleLocator.locator('.rule--operator'),
DATA_CONTRACT_SEMANTICS1.rules[0].operator
);
await selectOption(
page,
- ruleLocator.locator('.rule--value .ant-select'),
+ ruleLocator.locator('.rule--value'),
'admin',
true
);
@@ -1700,13 +1700,13 @@ test.describe('Data Contracts', () => {
const ruleLocator2 = page.locator('.rule').nth(1);
await selectOption(
page,
- ruleLocator2.locator('.rule--field .ant-select'),
+ ruleLocator2.locator('.rule--field'),
DATA_CONTRACT_SEMANTICS1.rules[1].field,
true
);
await selectOption(
page,
- ruleLocator2.locator('.rule--operator .ant-select'),
+ ruleLocator2.locator('.rule--operator'),
DATA_CONTRACT_SEMANTICS1.rules[1].operator
);
await page.getByTestId('save-semantic-button').click();
@@ -1720,13 +1720,13 @@ test.describe('Data Contracts', () => {
const ruleLocator3 = page.locator('.group').nth(2);
await selectOption(
page,
- ruleLocator3.locator('.group--field .ant-select'),
+ ruleLocator3.locator('.group--field'),
DATA_CONTRACT_SEMANTICS2.rules[0].field,
true
);
await selectOption(
page,
- ruleLocator3.locator('.rule--operator .ant-select'),
+ ruleLocator3.locator('.rule--operator'),
DATA_CONTRACT_SEMANTICS2.rules[0].operator
);
await page.getByTestId('save-semantic-button').click();
@@ -1772,18 +1772,18 @@ test.describe('Data Contracts', () => {
const ruleLocator = page.locator('.group').nth(0);
await selectOption(
page,
- ruleLocator.locator('.group--field .ant-select'),
+ ruleLocator.locator('.group--field'),
DATA_CONTRACT_SEMANTICS1.rules[0].field,
true
);
await selectOption(
page,
- ruleLocator.locator('.rule--operator .ant-select'),
+ ruleLocator.locator('.rule--operator'),
DATA_CONTRACT_SEMANTICS1.rules[0].operator
);
await selectOption(
page,
- ruleLocator.locator('.rule--value .ant-select'),
+ ruleLocator.locator('.rule--value'),
'admin',
true
);
@@ -1832,18 +1832,18 @@ test.describe('Data Contracts', () => {
const ruleLocator = page.locator('.group').nth(0);
await selectOption(
page,
- ruleLocator.locator('.group--field .ant-select'),
+ ruleLocator.locator('.group--field'),
DATA_CONTRACT_SEMANTICS1.rules[0].field,
true
);
await selectOption(
page,
- ruleLocator.locator('.rule--operator .ant-select'),
+ ruleLocator.locator('.rule--operator'),
DATA_CONTRACT_SEMANTICS1.rules[0].operator
);
await selectOption(
page,
- ruleLocator.locator('.rule--value .ant-select'),
+ ruleLocator.locator('.rule--value'),
'admin',
true
);
@@ -1858,13 +1858,13 @@ test.describe('Data Contracts', () => {
const ruleLocator3 = page.locator('.group').nth(2);
await selectOption(
page,
- ruleLocator3.locator('.group--field .ant-select'),
+ ruleLocator3.locator('.group--field'),
DATA_CONTRACT_SEMANTICS2.rules[0].field,
true
);
await selectOption(
page,
- ruleLocator3.locator('.rule--operator .ant-select'),
+ ruleLocator3.locator('.rule--operator'),
DATA_CONTRACT_SEMANTICS2.rules[0].operator
);
await page.getByTestId('save-semantic-button').click();
@@ -2369,18 +2369,18 @@ entitiesWithDataContracts.forEach((EntityClass) => {
const ruleLocator = page.locator('.group').nth(0);
await selectOption(
page,
- ruleLocator.locator('.group--field .ant-select'),
+ ruleLocator.locator('.group--field'),
DATA_CONTRACT_SEMANTICS1.rules[0].field,
true
);
await selectOption(
page,
- ruleLocator.locator('.rule--operator .ant-select'),
+ ruleLocator.locator('.rule--operator'),
DATA_CONTRACT_SEMANTICS1.rules[0].operator
);
await selectOption(
page,
- ruleLocator.locator('.rule--value .ant-select'),
+ ruleLocator.locator('.rule--value'),
'admin',
true
);
diff --git a/openmetadata-ui/src/main/resources/ui/playwright/e2e/Pages/DataContractsSemanticRules.spec.ts b/openmetadata-ui/src/main/resources/ui/playwright/e2e/Pages/DataContractsSemanticRules.spec.ts
index e7cf61eb9323..0336c30aebb6 100644
--- a/openmetadata-ui/src/main/resources/ui/playwright/e2e/Pages/DataContractsSemanticRules.spec.ts
+++ b/openmetadata-ui/src/main/resources/ui/playwright/e2e/Pages/DataContractsSemanticRules.spec.ts
@@ -104,18 +104,18 @@ test.describe('Data Contracts Semantics Rule Owner', () => {
const ruleLocator = page.locator('.group').nth(0);
await selectOption(
page,
- ruleLocator.locator('.group--field .ant-select'),
+ ruleLocator.locator('.group--field'),
'Owners',
true
);
await selectOption(
page,
- ruleLocator.locator('.rule--operator .ant-select'),
+ ruleLocator.locator('.rule--operator'),
DATA_CONTRACT_SEMANTIC_OPERATIONS.is
);
await selectOption(
page,
- ruleLocator.locator('.rule--value .ant-select'),
+ ruleLocator.locator('.rule--value'),
team.responseData.displayName,
true
);
@@ -198,18 +198,18 @@ test.describe('Data Contracts Semantics Rule Owner', () => {
const ruleLocator = page.locator('.group').nth(0);
await selectOption(
page,
- ruleLocator.locator('.group--field .ant-select'),
+ ruleLocator.locator('.group--field'),
'Owners',
true
);
await selectOption(
page,
- ruleLocator.locator('.rule--operator .ant-select'),
+ ruleLocator.locator('.rule--operator'),
DATA_CONTRACT_SEMANTIC_OPERATIONS.is_not
);
await selectOption(
page,
- ruleLocator.locator('.rule--value .ant-select'),
+ ruleLocator.locator('.rule--value'),
user.getUserDisplayName(),
true
);
@@ -299,18 +299,18 @@ test.describe('Data Contracts Semantics Rule Owner', () => {
const ruleLocator = page.locator('.group').nth(0);
await selectOption(
page,
- ruleLocator.locator('.group--field .ant-select'),
+ ruleLocator.locator('.group--field'),
'Owners',
true
);
await selectOption(
page,
- ruleLocator.locator('.rule--operator .ant-select'),
+ ruleLocator.locator('.rule--operator'),
DATA_CONTRACT_SEMANTIC_OPERATIONS.any_in
);
await selectOption(
page,
- ruleLocator.locator('.rule--value .ant-select'),
+ ruleLocator.locator('.rule--value'),
user.getUserDisplayName(),
true
);
@@ -399,18 +399,18 @@ test.describe('Data Contracts Semantics Rule Owner', () => {
const ruleLocator = page.locator('.group').nth(0);
await selectOption(
page,
- ruleLocator.locator('.group--field .ant-select'),
+ ruleLocator.locator('.group--field'),
'Owners',
true
);
await selectOption(
page,
- ruleLocator.locator('.rule--operator .ant-select'),
+ ruleLocator.locator('.rule--operator'),
DATA_CONTRACT_SEMANTIC_OPERATIONS.not_in
);
await selectOption(
page,
- ruleLocator.locator('.rule--value .ant-select'),
+ ruleLocator.locator('.rule--value'),
user.getUserDisplayName(),
true
);
@@ -492,13 +492,13 @@ test.describe('Data Contracts Semantics Rule Owner', () => {
const ruleLocator = page.locator('.group').nth(0);
await selectOption(
page,
- ruleLocator.locator('.group--field .ant-select'),
+ ruleLocator.locator('.group--field'),
'Owners',
true
);
await selectOption(
page,
- ruleLocator.locator('.rule--operator .ant-select'),
+ ruleLocator.locator('.rule--operator'),
DATA_CONTRACT_SEMANTIC_OPERATIONS.is_set
);
@@ -571,13 +571,13 @@ test.describe('Data Contracts Semantics Rule Owner', () => {
const ruleLocator = page.locator('.group').nth(0);
await selectOption(
page,
- ruleLocator.locator('.group--field .ant-select'),
+ ruleLocator.locator('.group--field'),
'Owners',
true
);
await selectOption(
page,
- ruleLocator.locator('.rule--operator .ant-select'),
+ ruleLocator.locator('.rule--operator'),
DATA_CONTRACT_SEMANTIC_OPERATIONS.is_not_set
);
@@ -652,13 +652,13 @@ test.describe('Data Contracts Semantics Rule Description', () => {
const ruleLocator = page.locator('.group').nth(0);
await selectOption(
page,
- ruleLocator.locator('.group--field .ant-select'),
+ ruleLocator.locator('.group--field'),
'Description',
true
);
await selectOption(
page,
- ruleLocator.locator('.rule--operator .ant-select'),
+ ruleLocator.locator('.rule--operator'),
DATA_CONTRACT_SEMANTIC_OPERATIONS.contains
);
@@ -739,13 +739,13 @@ test.describe('Data Contracts Semantics Rule Description', () => {
const ruleLocator = page.locator('.group').nth(0);
await selectOption(
page,
- ruleLocator.locator('.group--field .ant-select'),
+ ruleLocator.locator('.group--field'),
'Description',
true
);
await selectOption(
page,
- ruleLocator.locator('.rule--operator .ant-select'),
+ ruleLocator.locator('.rule--operator'),
DATA_CONTRACT_SEMANTIC_OPERATIONS.not_contains
);
const inputElement = ruleLocator.locator(
@@ -824,13 +824,13 @@ test.describe('Data Contracts Semantics Rule Description', () => {
const ruleLocator = page.locator('.group').nth(0);
await selectOption(
page,
- ruleLocator.locator('.group--field .ant-select'),
+ ruleLocator.locator('.group--field'),
'Description',
true
);
await selectOption(
page,
- ruleLocator.locator('.rule--operator .ant-select'),
+ ruleLocator.locator('.rule--operator'),
DATA_CONTRACT_SEMANTIC_OPERATIONS.is_set
);
@@ -908,13 +908,13 @@ test.describe('Data Contracts Semantics Rule Description', () => {
const ruleLocator = page.locator('.group').nth(0);
await selectOption(
page,
- ruleLocator.locator('.group--field .ant-select'),
+ ruleLocator.locator('.group--field'),
'Description',
true
);
await selectOption(
page,
- ruleLocator.locator('.rule--operator .ant-select'),
+ ruleLocator.locator('.rule--operator'),
DATA_CONTRACT_SEMANTIC_OPERATIONS.is_not_set
);
@@ -1037,18 +1037,18 @@ test.describe('Data Contracts Semantics Rule Domain', () => {
const ruleLocator = page.locator('.group').nth(0);
await selectOption(
page,
- ruleLocator.locator('.group--field .ant-select'),
+ ruleLocator.locator('.group--field'),
'Domain',
true
);
await selectOption(
page,
- ruleLocator.locator('.rule--operator .ant-select'),
+ ruleLocator.locator('.rule--operator'),
DATA_CONTRACT_SEMANTIC_OPERATIONS.is
);
await selectOption(
page,
- ruleLocator.locator('.rule--value .ant-select'),
+ ruleLocator.locator('.rule--value'),
domain1.responseData.name,
true
);
@@ -1119,18 +1119,18 @@ test.describe('Data Contracts Semantics Rule Domain', () => {
const ruleLocator = page.locator('.group').nth(0);
await selectOption(
page,
- ruleLocator.locator('.group--field .ant-select'),
+ ruleLocator.locator('.group--field'),
'Domain',
true
);
await selectOption(
page,
- ruleLocator.locator('.rule--operator .ant-select'),
+ ruleLocator.locator('.rule--operator'),
DATA_CONTRACT_SEMANTIC_OPERATIONS.is_not
);
await selectOption(
page,
- ruleLocator.locator('.rule--value .ant-select'),
+ ruleLocator.locator('.rule--value'),
domain2.responseData.name,
true
);
@@ -1202,18 +1202,18 @@ test.describe('Data Contracts Semantics Rule Domain', () => {
const ruleLocator = page.locator('.group').nth(0);
await selectOption(
page,
- ruleLocator.locator('.group--field .ant-select'),
+ ruleLocator.locator('.group--field'),
'Domain',
true
);
await selectOption(
page,
- ruleLocator.locator('.rule--operator .ant-select'),
+ ruleLocator.locator('.rule--operator'),
DATA_CONTRACT_SEMANTIC_OPERATIONS.any_in
);
await selectOption(
page,
- ruleLocator.locator('.rule--value .ant-select'),
+ ruleLocator.locator('.rule--value'),
domain1.responseData.name,
true
);
@@ -1283,18 +1283,18 @@ test.describe('Data Contracts Semantics Rule Domain', () => {
const ruleLocator = page.locator('.group').nth(0);
await selectOption(
page,
- ruleLocator.locator('.group--field .ant-select'),
+ ruleLocator.locator('.group--field'),
'Domain',
true
);
await selectOption(
page,
- ruleLocator.locator('.rule--operator .ant-select'),
+ ruleLocator.locator('.rule--operator'),
DATA_CONTRACT_SEMANTIC_OPERATIONS.not_in
);
await selectOption(
page,
- ruleLocator.locator('.rule--value .ant-select'),
+ ruleLocator.locator('.rule--value'),
domain1.responseData.name,
true
);
@@ -1364,13 +1364,13 @@ test.describe('Data Contracts Semantics Rule Domain', () => {
const ruleLocator = page.locator('.group').nth(0);
await selectOption(
page,
- ruleLocator.locator('.group--field .ant-select'),
+ ruleLocator.locator('.group--field'),
'Domain',
true
);
await selectOption(
page,
- ruleLocator.locator('.rule--operator .ant-select'),
+ ruleLocator.locator('.rule--operator'),
DATA_CONTRACT_SEMANTIC_OPERATIONS.is_set
);
@@ -1437,13 +1437,13 @@ test.describe('Data Contracts Semantics Rule Domain', () => {
const ruleLocator = page.locator('.group').nth(0);
await selectOption(
page,
- ruleLocator.locator('.group--field .ant-select'),
+ ruleLocator.locator('.group--field'),
'Domain',
true
);
await selectOption(
page,
- ruleLocator.locator('.rule--operator .ant-select'),
+ ruleLocator.locator('.rule--operator'),
DATA_CONTRACT_SEMANTIC_OPERATIONS.is_not_set
);
@@ -1516,13 +1516,13 @@ test.describe('Data Contracts Semantics Rule Version', () => {
const ruleLocator = page.locator('.group').nth(0);
await selectOption(
page,
- ruleLocator.locator('.group--field .ant-select'),
+ ruleLocator.locator('.group--field'),
'Version',
true
);
await selectOption(
page,
- ruleLocator.locator('.rule--operator .ant-select'),
+ ruleLocator.locator('.rule--operator'),
DATA_CONTRACT_SEMANTIC_OPERATIONS.is
);
@@ -1535,7 +1535,9 @@ test.describe('Data Contracts Semantics Rule Version', () => {
// (which starts at 0.1), ensuring the second edit always produces a diff
// and the save button stays enabled.
await ruleLocator
- .locator('.rule--value .rule--widget--NUMBER .ant-input-number-input')
+ .locator(
+ '.rule--value .rule--widget--NUMBER input[data-testid="qb-number-input"]'
+ )
.fill('99.9');
contractId = (
@@ -1566,7 +1568,9 @@ test.describe('Data Contracts Semantics Rule Version', () => {
const versionInput = page
.locator('.group')
.first()
- .locator('.rule--value .rule--widget--NUMBER .ant-input-number-input');
+ .locator(
+ '.rule--value .rule--widget--NUMBER input[data-testid="qb-number-input"]'
+ );
await versionInput.clear();
await versionInput.fill(actualVersion);
@@ -1629,13 +1633,13 @@ test.describe('Data Contracts Semantics Rule Version', () => {
const ruleLocator = page.locator('.group').nth(0);
await selectOption(
page,
- ruleLocator.locator('.group--field .ant-select'),
+ ruleLocator.locator('.group--field'),
'Version',
true
);
await selectOption(
page,
- ruleLocator.locator('.rule--operator .ant-select'),
+ ruleLocator.locator('.rule--operator'),
DATA_CONTRACT_SEMANTIC_OPERATIONS.is_not
);
@@ -1648,7 +1652,9 @@ test.describe('Data Contracts Semantics Rule Version', () => {
// (which starts at 0.1), ensuring the second edit always produces a diff
// and the save button stays enabled.
await ruleLocator
- .locator('.rule--value .rule--widget--NUMBER .ant-input-number-input')
+ .locator(
+ '.rule--value .rule--widget--NUMBER input[data-testid="qb-number-input"]'
+ )
.fill('99.9');
contractId = (
@@ -1684,7 +1690,9 @@ test.describe('Data Contracts Semantics Rule Version', () => {
const versionInput = page
.locator('.group')
.first()
- .locator('.rule--value .rule--widget--NUMBER .ant-input-number-input');
+ .locator(
+ '.rule--value .rule--widget--NUMBER input[data-testid="qb-number-input"]'
+ );
await versionInput.clear();
await versionInput.fill(domainBumpedVersion);
@@ -1743,20 +1751,22 @@ test.describe('Data Contracts Semantics Rule Version', () => {
const ruleLocator = page.locator('.group').nth(0);
await selectOption(
page,
- ruleLocator.locator('.group--field .ant-select'),
+ ruleLocator.locator('.group--field'),
'Version',
true
);
await selectOption(
page,
- ruleLocator.locator('.rule--operator .ant-select'),
+ ruleLocator.locator('.rule--operator'),
DATA_CONTRACT_SEMANTIC_OPERATIONS.less
);
// Use 99.9 — any realistic entity version is always below this, so the
// check passes regardless of how many version bumps CI introduces.
await ruleLocator
- .locator('.rule--value .rule--widget--NUMBER .ant-input-number-input')
+ .locator(
+ '.rule--value .rule--widget--NUMBER input[data-testid="qb-number-input"]'
+ )
.fill('99.9');
await saveAndTriggerDataContractValidation(page, true);
@@ -1778,7 +1788,9 @@ test.describe('Data Contracts Semantics Rule Version', () => {
const versionInput = page
.locator('.group')
.first()
- .locator('.rule--value .rule--widget--NUMBER .ant-input-number-input');
+ .locator(
+ '.rule--value .rule--widget--NUMBER input[data-testid="qb-number-input"]'
+ );
await versionInput.clear();
await versionInput.fill('0.01');
@@ -1820,20 +1832,22 @@ test.describe('Data Contracts Semantics Rule Version', () => {
const ruleLocator = page.locator('.group').nth(0);
await selectOption(
page,
- ruleLocator.locator('.group--field .ant-select'),
+ ruleLocator.locator('.group--field'),
'Version',
true
);
await selectOption(
page,
- ruleLocator.locator('.rule--operator .ant-select'),
+ ruleLocator.locator('.rule--operator'),
DATA_CONTRACT_SEMANTIC_OPERATIONS.greater
);
// Use 99.9 — any realistic entity version is always below this, so
// entity_version > 99.9 always fails regardless of version bumps in CI.
await ruleLocator
- .locator('.rule--value .rule--widget--NUMBER .ant-input-number-input')
+ .locator(
+ '.rule--value .rule--widget--NUMBER input[data-testid="qb-number-input"]'
+ )
.fill('99.9');
await saveAndTriggerDataContractValidation(page, true);
@@ -1856,7 +1870,9 @@ test.describe('Data Contracts Semantics Rule Version', () => {
const versionInput = page
.locator('.group')
.first()
- .locator('.rule--value .rule--widget--NUMBER .ant-input-number-input');
+ .locator(
+ '.rule--value .rule--widget--NUMBER input[data-testid="qb-number-input"]'
+ );
await versionInput.clear();
await versionInput.fill('0.01');
@@ -1900,20 +1916,22 @@ test.describe('Data Contracts Semantics Rule Version', () => {
const ruleLocator = page.locator('.group').nth(0);
await selectOption(
page,
- ruleLocator.locator('.group--field .ant-select'),
+ ruleLocator.locator('.group--field'),
'Version',
true
);
await selectOption(
page,
- ruleLocator.locator('.rule--operator .ant-select'),
+ ruleLocator.locator('.rule--operator'),
DATA_CONTRACT_SEMANTIC_OPERATIONS.less_equal
);
// Use 99.9 — any realistic entity version is always below this, so
// entity_version <= 99.9 always passes regardless of version bumps in CI.
await ruleLocator
- .locator('.rule--value .rule--widget--NUMBER .ant-input-number-input')
+ .locator(
+ '.rule--value .rule--widget--NUMBER input[data-testid="qb-number-input"]'
+ )
.fill('99.9');
await saveAndTriggerDataContractValidation(page, true);
@@ -1935,7 +1953,9 @@ test.describe('Data Contracts Semantics Rule Version', () => {
const versionInput = page
.locator('.group')
.first()
- .locator('.rule--value .rule--widget--NUMBER .ant-input-number-input');
+ .locator(
+ '.rule--value .rule--widget--NUMBER input[data-testid="qb-number-input"]'
+ );
await versionInput.clear();
await versionInput.fill('0.01');
@@ -1980,20 +2000,22 @@ test.describe('Data Contracts Semantics Rule Version', () => {
const ruleLocator = page.locator('.group').nth(0);
await selectOption(
page,
- ruleLocator.locator('.group--field .ant-select'),
+ ruleLocator.locator('.group--field'),
'Version',
true
);
await selectOption(
page,
- ruleLocator.locator('.rule--operator .ant-select'),
+ ruleLocator.locator('.rule--operator'),
DATA_CONTRACT_SEMANTIC_OPERATIONS.greater_equal
);
// Use 99.9 — any realistic entity version is always below this, so
// entity_version >= 99.9 always fails regardless of version bumps in CI.
await ruleLocator
- .locator('.rule--value .rule--widget--NUMBER .ant-input-number-input')
+ .locator(
+ '.rule--value .rule--widget--NUMBER input[data-testid="qb-number-input"]'
+ )
.fill('99.9');
await saveAndTriggerDataContractValidation(page, true);
@@ -2015,7 +2037,9 @@ test.describe('Data Contracts Semantics Rule Version', () => {
const versionInput = page
.locator('.group')
.first()
- .locator('.rule--value .rule--widget--NUMBER .ant-input-number-input');
+ .locator(
+ '.rule--value .rule--widget--NUMBER input[data-testid="qb-number-input"]'
+ );
await versionInput.clear();
await versionInput.fill('0.01');
@@ -2084,19 +2108,19 @@ test.describe('Data Contracts Semantics Rule DataProduct', () => {
const ruleLocator = page.locator('.group').nth(0);
await selectOption(
page,
- ruleLocator.locator('.group--field .ant-select'),
+ ruleLocator.locator('.group--field'),
'Data Product',
true
);
await selectOption(
page,
- ruleLocator.locator('.rule--operator .ant-select'),
+ ruleLocator.locator('.rule--operator'),
DATA_CONTRACT_SEMANTIC_OPERATIONS.is
);
await selectOption(
page,
- ruleLocator.locator('.rule--value .ant-select'),
+ ruleLocator.locator('.rule--value'),
createdDataProducts[0].responseData.name,
true
);
@@ -2179,19 +2203,19 @@ test.describe('Data Contracts Semantics Rule DataProduct', () => {
const ruleLocator = page.locator('.group').nth(0);
await selectOption(
page,
- ruleLocator.locator('.group--field .ant-select'),
+ ruleLocator.locator('.group--field'),
'Data Product',
true
);
await selectOption(
page,
- ruleLocator.locator('.rule--operator .ant-select'),
+ ruleLocator.locator('.rule--operator'),
DATA_CONTRACT_SEMANTIC_OPERATIONS.is_not
);
await selectOption(
page,
- ruleLocator.locator('.rule--value .ant-select'),
+ ruleLocator.locator('.rule--value'),
createdDataProducts[0].responseData.name,
true
);
@@ -2274,18 +2298,18 @@ test.describe('Data Contracts Semantics Rule DataProduct', () => {
const ruleLocator = page.locator('.group').nth(0);
await selectOption(
page,
- ruleLocator.locator('.group--field .ant-select'),
+ ruleLocator.locator('.group--field'),
'Data Product',
true
);
await selectOption(
page,
- ruleLocator.locator('.rule--operator .ant-select'),
+ ruleLocator.locator('.rule--operator'),
DATA_CONTRACT_SEMANTIC_OPERATIONS.any_in
);
await selectOption(
page,
- ruleLocator.locator('.rule--value .ant-select'),
+ ruleLocator.locator('.rule--value'),
createdDataProducts[0].responseData.name,
true
);
@@ -2367,18 +2391,18 @@ test.describe('Data Contracts Semantics Rule DataProduct', () => {
const ruleLocator = page.locator('.group').nth(0);
await selectOption(
page,
- ruleLocator.locator('.group--field .ant-select'),
+ ruleLocator.locator('.group--field'),
'Data Product',
true
);
await selectOption(
page,
- ruleLocator.locator('.rule--operator .ant-select'),
+ ruleLocator.locator('.rule--operator'),
DATA_CONTRACT_SEMANTIC_OPERATIONS.not_in
);
await selectOption(
page,
- ruleLocator.locator('.rule--value .ant-select'),
+ ruleLocator.locator('.rule--value'),
createdDataProducts[0].responseData.name,
true
);
@@ -2495,13 +2519,13 @@ test.describe('Data Contracts Semantics Rule DataProduct', () => {
const ruleLocator = page.locator('.group').nth(0);
await selectOption(
page,
- ruleLocator.locator('.group--field .ant-select'),
+ ruleLocator.locator('.group--field'),
'Data Product',
true
);
await selectOption(
page,
- ruleLocator.locator('.rule--operator .ant-select'),
+ ruleLocator.locator('.rule--operator'),
DATA_CONTRACT_SEMANTIC_OPERATIONS.is_set
);
@@ -2576,13 +2600,13 @@ test.describe('Data Contracts Semantics Rule DataProduct', () => {
const ruleLocator = page.locator('.group').nth(0);
await selectOption(
page,
- ruleLocator.locator('.group--field .ant-select'),
+ ruleLocator.locator('.group--field'),
'Data Product',
true
);
await selectOption(
page,
- ruleLocator.locator('.rule--operator .ant-select'),
+ ruleLocator.locator('.rule--operator'),
DATA_CONTRACT_SEMANTIC_OPERATIONS.is_not_set
);
@@ -2663,19 +2687,19 @@ test.describe('Data Contracts Semantics Rule DisplayName', () => {
const ruleLocator = page.locator('.group').nth(0);
await selectOption(
page,
- ruleLocator.locator('.group--field .ant-select'),
+ ruleLocator.locator('.group--field'),
'Display Name',
true
);
await selectOption(
page,
- ruleLocator.locator('.rule--operator .ant-select'),
+ ruleLocator.locator('.rule--operator'),
DATA_CONTRACT_SEMANTIC_OPERATIONS.is
);
await selectOption(
page,
- ruleLocator.locator('.rule--value .ant-select'),
+ ruleLocator.locator('.rule--value'),
table.entityResponseData.displayName || '',
true
);
@@ -2750,19 +2774,19 @@ test.describe('Data Contracts Semantics Rule DisplayName', () => {
const ruleLocator = page.locator('.group').nth(0);
await selectOption(
page,
- ruleLocator.locator('.group--field .ant-select'),
+ ruleLocator.locator('.group--field'),
'Display Name',
true
);
await selectOption(
page,
- ruleLocator.locator('.rule--operator .ant-select'),
+ ruleLocator.locator('.rule--operator'),
DATA_CONTRACT_SEMANTIC_OPERATIONS.is_not
);
await selectOption(
page,
- ruleLocator.locator('.rule--value .ant-select'),
+ ruleLocator.locator('.rule--value'),
table.entityResponseData.displayName || '',
true
);
@@ -2836,18 +2860,18 @@ test.describe('Data Contracts Semantics Rule DisplayName', () => {
const ruleLocator = page.locator('.group').nth(0);
await selectOption(
page,
- ruleLocator.locator('.group--field .ant-select'),
+ ruleLocator.locator('.group--field'),
'Display Name',
true
);
await selectOption(
page,
- ruleLocator.locator('.rule--operator .ant-select'),
+ ruleLocator.locator('.rule--operator'),
DATA_CONTRACT_SEMANTIC_OPERATIONS.any_in
);
await selectOption(
page,
- ruleLocator.locator('.rule--value .ant-select'),
+ ruleLocator.locator('.rule--value'),
table.entityResponseData.displayName || '',
true
);
@@ -2922,18 +2946,18 @@ test.describe('Data Contracts Semantics Rule DisplayName', () => {
const ruleLocator = page.locator('.group').nth(0);
await selectOption(
page,
- ruleLocator.locator('.group--field .ant-select'),
+ ruleLocator.locator('.group--field'),
'Display Name',
true
);
await selectOption(
page,
- ruleLocator.locator('.rule--operator .ant-select'),
+ ruleLocator.locator('.rule--operator'),
DATA_CONTRACT_SEMANTIC_OPERATIONS.not_in
);
await selectOption(
page,
- ruleLocator.locator('.rule--value .ant-select'),
+ ruleLocator.locator('.rule--value'),
table.entityResponseData.displayName || '',
true
);
@@ -3007,13 +3031,13 @@ test.describe('Data Contracts Semantics Rule DisplayName', () => {
const ruleLocator = page.locator('.group').nth(0);
await selectOption(
page,
- ruleLocator.locator('.group--field .ant-select'),
+ ruleLocator.locator('.group--field'),
'Display Name',
true
);
await selectOption(
page,
- ruleLocator.locator('.rule--operator .ant-select'),
+ ruleLocator.locator('.rule--operator'),
DATA_CONTRACT_SEMANTIC_OPERATIONS.is_set
);
@@ -3088,13 +3112,13 @@ test.describe('Data Contracts Semantics Rule DisplayName', () => {
const ruleLocator = page.locator('.group').nth(0);
await selectOption(
page,
- ruleLocator.locator('.group--field .ant-select'),
+ ruleLocator.locator('.group--field'),
'Display Name',
true
);
await selectOption(
page,
- ruleLocator.locator('.rule--operator .ant-select'),
+ ruleLocator.locator('.rule--operator'),
DATA_CONTRACT_SEMANTIC_OPERATIONS.is_not_set
);
@@ -3168,20 +3192,20 @@ test.describe('Data Contracts Semantics Rule Updated on', () => {
const ruleLocator = page.locator('.group').nth(0);
await selectOption(
page,
- ruleLocator.locator('.group--field .ant-select'),
+ ruleLocator.locator('.group--field'),
'Updated on',
true
);
await selectOption(
page,
- ruleLocator.locator('.rule--operator .ant-select'),
+ ruleLocator.locator('.rule--operator'),
DATA_CONTRACT_SEMANTIC_OPERATIONS.between
);
- const startDate = customFormatDateTime(getCurrentMillis(), 'dd.MM.yyyy');
+ const startDate = customFormatDateTime(getCurrentMillis(), 'yyyy-MM-dd');
const endDate = customFormatDateTime(
getEpochMillisForFutureDays(5),
- 'dd.MM.yyyy'
+ 'yyyy-MM-dd'
);
await selectRange(page, ruleLocator, startDate, endDate);
@@ -3210,11 +3234,14 @@ test.describe('Data Contracts Semantics Rule Updated on', () => {
const newStart = customFormatDateTime(
getEpochMillisForFutureDays(1),
- 'dd.MM.yyyy'
+ 'yyyy-MM-dd'
);
- page.getByRole('textbox', { name: 'Enter date from' }).fill(newStart);
- await page.press('.ant-picker-input-active input', 'Enter');
- await page.press('.ant-picker-input-active input', 'Enter');
+ await page
+ .locator('.group')
+ .nth(0)
+ .locator('.rule--value input[type="date"]')
+ .nth(0)
+ .fill(newStart);
// save and trigger contract validation
await saveAndTriggerDataContractValidation(page, true);
@@ -3255,20 +3282,20 @@ test.describe('Data Contracts Semantics Rule Updated on', () => {
const ruleLocator = page.locator('.group').nth(0);
await selectOption(
page,
- ruleLocator.locator('.group--field .ant-select'),
+ ruleLocator.locator('.group--field'),
'Updated on',
true
);
await selectOption(
page,
- ruleLocator.locator('.rule--operator .ant-select'),
+ ruleLocator.locator('.rule--operator'),
DATA_CONTRACT_SEMANTIC_OPERATIONS.not_between
);
- const startDate = customFormatDateTime(getCurrentMillis(), 'dd.MM.yyyy');
+ const startDate = customFormatDateTime(getCurrentMillis(), 'yyyy-MM-dd');
const endDate = customFormatDateTime(
getEpochMillisForFutureDays(5),
- 'dd.MM.yyyy'
+ 'yyyy-MM-dd'
);
await selectRange(page, ruleLocator, startDate, endDate);
@@ -3298,23 +3325,14 @@ test.describe('Data Contracts Semantics Rule Updated on', () => {
const newStart = customFormatDateTime(
getEpochMillisForFutureDays(1),
- 'dd.MM.yyyy'
+ 'yyyy-MM-dd'
);
await page
.locator('.group')
.nth(0)
- .locator('.rule--value .ant-picker-range')
- .click();
-
- await page.locator('.ant-picker-dropdown-range').waitFor({
- state: 'visible',
- });
-
- await page
- .getByRole('textbox', { name: 'Enter date from' })
+ .locator('.rule--value input[type="date"]')
+ .nth(0)
.fill(newStart);
- await page.press('.ant-picker-input-active input', 'Enter');
- await page.press('.ant-picker-input-active input', 'Enter');
// save and trigger contract validation
await saveAndTriggerDataContractValidation(page, true);
@@ -3354,24 +3372,19 @@ test.describe('Data Contracts Semantics Rule Updated on', () => {
const ruleLocator = page.locator('.group').nth(0);
await selectOption(
page,
- ruleLocator.locator('.group--field .ant-select'),
+ ruleLocator.locator('.group--field'),
'Updated on',
true
);
await selectOption(
page,
- ruleLocator.locator('.rule--operator .ant-select'),
+ ruleLocator.locator('.rule--operator'),
DATA_CONTRACT_SEMANTIC_OPERATIONS.less
);
- const date = customFormatDateTime(getCurrentMillis(), 'dd.MM.yyyy');
+ const date = customFormatDateTime(getCurrentMillis(), 'yyyy-MM-dd');
- await ruleLocator.locator('.rule--value .ant-picker').click();
- await page.locator('.ant-picker-dropdown').waitFor({
- state: 'visible',
- });
- await page.locator('.ant-picker-input input').fill(date);
- await page.press('.ant-picker-input input', 'Enter');
+ await ruleLocator.locator('.rule--value input[type="date"]').fill(date);
// save and trigger contract validation
await saveAndTriggerDataContractValidation(page, true);
@@ -3398,19 +3411,14 @@ test.describe('Data Contracts Semantics Rule Updated on', () => {
const newDate = customFormatDateTime(
getEpochMillisForFutureDays(1),
- 'dd.MM.yyyy'
+ 'yyyy-MM-dd'
);
await page
.locator('.group')
.nth(0)
- .locator('.rule--value .ant-picker')
- .click();
- await page.locator('.ant-picker-dropdown').waitFor({
- state: 'visible',
- });
- await page.locator('.ant-picker-input input').fill(newDate);
- await page.press('.ant-picker-input input', 'Enter');
+ .locator('.rule--value input[type="date"]')
+ .fill(newDate);
// save and trigger contract validation
await saveAndTriggerDataContractValidation(page, true);
@@ -3450,27 +3458,22 @@ test.describe('Data Contracts Semantics Rule Updated on', () => {
const ruleLocator = page.locator('.group').nth(0);
await selectOption(
page,
- ruleLocator.locator('.group--field .ant-select'),
+ ruleLocator.locator('.group--field'),
'Updated on',
true
);
await selectOption(
page,
- ruleLocator.locator('.rule--operator .ant-select'),
+ ruleLocator.locator('.rule--operator'),
DATA_CONTRACT_SEMANTIC_OPERATIONS.greater
);
const date = customFormatDateTime(
getEpochMillisForFutureDays(1),
- 'dd.MM.yyyy'
+ 'yyyy-MM-dd'
);
- await ruleLocator.locator('.rule--value .ant-picker').click();
- await page.locator('.ant-picker-dropdown').waitFor({
- state: 'visible',
- });
- await page.locator('.ant-picker-input input').fill(date);
- await page.press('.ant-picker-input input', 'Enter');
+ await ruleLocator.locator('.rule--value input[type="date"]').fill(date);
// save and trigger contract validation
await saveAndTriggerDataContractValidation(page, true);
@@ -3497,19 +3500,14 @@ test.describe('Data Contracts Semantics Rule Updated on', () => {
const newDate = customFormatDateTime(
getEpochMillisForFutureDays(-1),
- 'dd.MM.yyyy'
+ 'yyyy-MM-dd'
);
await page
.locator('.group')
.nth(0)
- .locator('.rule--value .ant-picker')
- .click();
- await page.locator('.ant-picker-dropdown').waitFor({
- state: 'visible',
- });
- await page.locator('.ant-picker-input input').fill(newDate);
- await page.press('.ant-picker-input input', 'Enter');
+ .locator('.rule--value input[type="date"]')
+ .fill(newDate);
// save and trigger contract validation
await saveAndTriggerDataContractValidation(page, true);
@@ -3549,27 +3547,22 @@ test.describe('Data Contracts Semantics Rule Updated on', () => {
const ruleLocator = page.locator('.group').nth(0);
await selectOption(
page,
- ruleLocator.locator('.group--field .ant-select'),
+ ruleLocator.locator('.group--field'),
'Updated on',
true
);
await selectOption(
page,
- ruleLocator.locator('.rule--operator .ant-select'),
+ ruleLocator.locator('.rule--operator'),
DATA_CONTRACT_SEMANTIC_OPERATIONS.less_equal
);
const date = customFormatDateTime(
getEpochMillisForFutureDays(1),
- 'dd.MM.yyyy'
+ 'yyyy-MM-dd'
);
- await ruleLocator.locator('.rule--value .ant-picker').click();
- await page.locator('.ant-picker-dropdown').waitFor({
- state: 'visible',
- });
- await page.locator('.ant-picker-input input').fill(date);
- await page.press('.ant-picker-input input', 'Enter');
+ await ruleLocator.locator('.rule--value input[type="date"]').fill(date);
// save and trigger contract validation
await saveAndTriggerDataContractValidation(page, true);
@@ -3595,19 +3588,14 @@ test.describe('Data Contracts Semantics Rule Updated on', () => {
const newDate = customFormatDateTime(
getEpochMillisForFutureDays(-1),
- 'dd.MM.yyyy'
+ 'yyyy-MM-dd'
);
await page
.locator('.group')
.nth(0)
- .locator('.rule--value .ant-picker')
- .click();
- await page.locator('.ant-picker-dropdown').waitFor({
- state: 'visible',
- });
- await page.locator('.ant-picker-input input').fill(newDate);
- await page.press('.ant-picker-input input', 'Enter');
+ .locator('.rule--value input[type="date"]')
+ .fill(newDate);
// save and trigger contract validation
await saveAndTriggerDataContractValidation(page, true);
@@ -3651,27 +3639,22 @@ test.describe('Data Contracts Semantics Rule Updated on', () => {
const ruleLocator = page.locator('.group').nth(0);
await selectOption(
page,
- ruleLocator.locator('.group--field .ant-select'),
+ ruleLocator.locator('.group--field'),
'Updated on',
true
);
await selectOption(
page,
- ruleLocator.locator('.rule--operator .ant-select'),
+ ruleLocator.locator('.rule--operator'),
DATA_CONTRACT_SEMANTIC_OPERATIONS.greater_equal
);
const date = customFormatDateTime(
getEpochMillisForFutureDays(-1),
- 'dd.MM.yyyy'
+ 'yyyy-MM-dd'
);
- await ruleLocator.locator('.rule--value .ant-picker').click();
- await page.locator('.ant-picker-dropdown').waitFor({
- state: 'visible',
- });
- await page.locator('.ant-picker-input input').fill(date);
- await page.press('.ant-picker-input input', 'Enter');
+ await ruleLocator.locator('.rule--value input[type="date"]').fill(date);
// save and trigger contract validation
await saveAndTriggerDataContractValidation(page, true);
@@ -3697,19 +3680,14 @@ test.describe('Data Contracts Semantics Rule Updated on', () => {
const newDate = customFormatDateTime(
getEpochMillisForFutureDays(1),
- 'dd.MM.yyyy'
+ 'yyyy-MM-dd'
);
await page
.locator('.group')
.nth(0)
- .locator('.rule--value .ant-picker')
- .click();
- await page.locator('.ant-picker-dropdown').waitFor({
- state: 'visible',
- });
- await page.locator('.ant-picker-input input').fill(newDate);
- await page.press('.ant-picker-input input', 'Enter');
+ .locator('.rule--value input[type="date"]')
+ .fill(newDate);
// save and trigger contract validation
await saveAndTriggerDataContractValidation(page, true);
@@ -3764,13 +3742,13 @@ test.describe('Data Contract - Semantics Fields Validation', () => {
const ruleLocator = page.locator('.group').nth(0);
await selectOption(
page,
- ruleLocator.locator('.group--field .ant-select'),
+ ruleLocator.locator('.group--field'),
'Owners',
true
);
await selectOption(
page,
- ruleLocator.locator('.rule--operator .ant-select'),
+ ruleLocator.locator('.rule--operator'),
'Is Set'
);
});
@@ -3790,17 +3768,8 @@ test.describe('Data Contract - Semantics Fields Validation', () => {
});
await test.step('select Is Set operator and error is hidden', async () => {
- await selectOption(
- page,
- page.locator('.rule--field .ant-select'),
- 'Owners',
- true
- );
- await selectOption(
- page,
- page.locator('.rule--operator .ant-select'),
- 'Is Set'
- );
+ await selectOption(page, page.locator('.rule--field'), 'Owners', true);
+ await selectOption(page, page.locator('.rule--operator'), 'Is Set');
await expect(page.getByText(/rule is required/i)).not.toBeVisible();
});
});
diff --git a/openmetadata-ui/src/main/resources/ui/playwright/e2e/Pages/DataMarketplace.spec.ts b/openmetadata-ui/src/main/resources/ui/playwright/e2e/Pages/DataMarketplace.spec.ts
index d711e936791b..1d12c9a50a08 100644
--- a/openmetadata-ui/src/main/resources/ui/playwright/e2e/Pages/DataMarketplace.spec.ts
+++ b/openmetadata-ui/src/main/resources/ui/playwright/e2e/Pages/DataMarketplace.spec.ts
@@ -21,7 +21,11 @@ import {
navigateToMarketplace,
searchMarketplace,
} from '../../utils/dataMarketplace';
-import { fillCommonFormItems, fillDomainForm } from '../../utils/domain';
+import {
+ clickDrawerSave,
+ fillCommonFormItems,
+ fillDomainForm,
+} from '../../utils/domain';
import { waitForAllLoadersToDisappear } from '../../utils/entity';
import { test } from '../fixtures/pages';
@@ -173,7 +177,10 @@ test.describe(
response.url().includes('/api/v1/dataProducts') &&
response.request().method() === 'POST'
);
- await page.getByTestId('save-btn').click();
+ const saveBtn = page.getByTestId('save-btn');
+ await expect(saveBtn).toBeVisible();
+ await saveBtn.focus();
+ await page.keyboard.press('Enter');
const response = await createResponse;
expect(response.status()).toBe(201);
});
@@ -214,7 +221,7 @@ test.describe(
response.url().includes('/api/v1/domains') &&
response.request().method() === 'POST'
);
- await page.getByTestId('save-btn').click();
+ await clickDrawerSave(page);
const response = await createResponse;
expect(response.status()).toBe(201);
});
diff --git a/openmetadata-ui/src/main/resources/ui/playwright/e2e/Pages/IntakeForm.spec.ts b/openmetadata-ui/src/main/resources/ui/playwright/e2e/Pages/IntakeForm.spec.ts
index 810394e0478f..5ae24cdcf8ed 100644
--- a/openmetadata-ui/src/main/resources/ui/playwright/e2e/Pages/IntakeForm.spec.ts
+++ b/openmetadata-ui/src/main/resources/ui/playwright/e2e/Pages/IntakeForm.spec.ts
@@ -17,7 +17,7 @@ import { Glossary } from '../../support/glossary/Glossary';
import { GlossaryTerm } from '../../support/glossary/GlossaryTerm';
import { performAdminLogin } from '../../utils/admin';
import { descriptionBox, redirectToHomePage, uuid } from '../../utils/common';
-import { fillDomainForm } from '../../utils/domain';
+import { clickDrawerSave, fillDomainForm } from '../../utils/domain';
import { waitForAllLoadersToDisappear } from '../../utils/entity';
import { openAddGlossaryTermModal } from '../../utils/glossary';
import { sidebarClick } from '../../utils/sidebar';
@@ -210,7 +210,16 @@ const selectExtensionReference = async ({
.first();
await expect(input).toBeVisible({ timeout: 15000 });
- await input.click();
+ // Use force:true to bypass Playwright's element-stability check. The
+ // reference-picker input lives inside a drawer whose body re-renders while
+ // the form settles, causing the input's bounding box to shift. A plain
+ // click() would spin until the test times out ("element is not stable");
+ // force:true skips that check while still dispatching the pointer events
+ // that activate the ComboBox popup. Do NOT use focus() here — opening the
+ // popup without pointer activation leaves the ComboBox in a state where the
+ // subsequent option.click() triggers unexpected re-renders that continuously
+ // detach the option element, causing a 3-minute hang.
+ await input.click({ force: true });
await input.fill(query);
await searchResponse;
@@ -629,7 +638,7 @@ test.describe(
}
};
page.on('response', postListener);
- await page.getByTestId('save-btn').click();
+ await clickDrawerSave(page);
// Poll for up to 3s and confirm no POST ever fires. We intentionally
// avoid `page.waitForTimeout` (linted as flaky) and instead use
@@ -1073,7 +1082,7 @@ test.describe(
r.url().endsWith('/api/v1/dataProducts') &&
r.request().method() === 'POST'
);
- await page.getByTestId('save-btn').click();
+ await clickDrawerSave(page);
const response = await createResponse;
expect(response.status()).toBe(201);
@@ -1414,7 +1423,7 @@ test.describe(
}
};
page.on('request', trackCreateRequest);
- await page.getByTestId('save-btn').click();
+ await clickDrawerSave(page);
await expect(
page.getByText('URL must use http or https protocol')
).toBeVisible();
@@ -1433,7 +1442,7 @@ test.describe(
response.url().endsWith('/api/v1/dataProducts') &&
response.request().method() === 'POST'
);
- await page.getByTestId('save-btn').click();
+ await clickDrawerSave(page);
const request = await createRequest;
const response = await createResponse;
@@ -1563,7 +1572,7 @@ test.describe(
response.url().endsWith('/api/v1/domains') &&
response.request().method() === 'POST'
);
- await page.getByTestId('save-btn').click();
+ await clickDrawerSave(page);
const request = await createRequest;
const response = await createResponse;
diff --git a/openmetadata-ui/src/main/resources/ui/playwright/utils/advancedSearch.ts b/openmetadata-ui/src/main/resources/ui/playwright/utils/advancedSearch.ts
index 24cb7a7b6d95..cf4c9d464a6f 100644
--- a/openmetadata-ui/src/main/resources/ui/playwright/utils/advancedSearch.ts
+++ b/openmetadata-ui/src/main/resources/ui/playwright/utils/advancedSearch.ts
@@ -12,7 +12,7 @@
*/
import { expect, Locator, Page } from '@playwright/test';
import { clickOutside } from './common';
-import { escapeESReservedCharacters, getEncodedFqn } from './entity';
+import { getEncodedFqn } from './entity';
type EntityFields = {
id: string;
@@ -174,52 +174,86 @@ export const selectOption = async (
optionTitle: string,
isSearchable = false
) => {
- if (isSearchable) {
- // Wait for dropdown to be visible before clicking
- const selector = dropdownLocator.locator('.ant-select-selector');
- await expect(selector).toBeVisible();
- await selector.click();
-
- await dropdownLocator
- .locator('.ant-select-arrow-loading svg[data-icon="loading"]')
- .waitFor({ state: 'detached' });
-
- // Clear any existing input and type the new value
- const combobox = dropdownLocator.getByRole('combobox');
- await combobox.clear();
-
- await dropdownLocator
- .locator('.ant-select-arrow-loading svg[data-icon="loading"]')
- .waitFor({ state: 'detached' });
+ const comboboxInput = dropdownLocator.locator('input[role="combobox"]');
+ const triggerButton = dropdownLocator.locator(
+ 'button[aria-haspopup="listbox"]'
+ );
- await combobox.fill(optionTitle);
+ await expect(comboboxInput.or(triggerButton).first()).toBeVisible();
- await dropdownLocator
- .locator('.ant-select-arrow-loading svg[data-icon="loading"]')
- .waitFor({ state: 'detached' });
+ if (isSearchable) {
+ if ((await triggerButton.count()) === 0) {
+ // MultiSelect: no chevron overlays the input, so clicking is safe —
+ // and required, since its popup opens from a mousedown handler.
+ await comboboxInput.click();
+ }
+ // Single fill (no clear first) — one input event, one async fetch.
+ await comboboxInput.fill(optionTitle);
+ // React Aria may close the focus-opened popup while processing the atomic
+ // fill; ArrowDown deterministically (re)opens it with the filter applied.
+ await comboboxInput.press('ArrowDown');
+ } else if ((await comboboxInput.count()) > 0) {
+ // Select.ComboBox: fill('') focuses the input (menuTrigger="focus" opens
+ // the popup) and clears the current-label filter so all options show.
+ // Never pointer-click the input — in narrow ComboBoxes (e.g. the RAQB
+ // operator column) the absolutely positioned chevron button covers the
+ // input's center and intercepts the click, hanging actionability retries.
+ await comboboxInput.fill('');
+ await comboboxInput.press('ArrowDown');
} else {
- await dropdownLocator.click();
+ // Plain Select (no combobox input): click the trigger button to open.
+ await triggerButton.click();
}
- await expect(dropdownLocator).toHaveClass(/(^|\s)ant-select-focused(\s|$)/);
-
- await page.locator('.ant-select-dropdown:visible').first().waitFor({
- state: 'visible',
- });
-
- // CRITICAL: Use :visible selector chain pattern (Rule 4 from deflake guide)
- // Use .first() to handle multiple matches (acceptable when scoped to visible dropdown)
- const optionLocator = page
- .locator('.ant-select-dropdown:visible')
- .getByTitle(optionTitle, { exact: true })
- .first();
- await expect(optionLocator).toBeVisible();
-
- // Wait for dropdown animations to settle before clicking
- // This prevents "element detached from DOM" errors during re-renders
- // eslint-disable-next-line playwright/no-wait-for-timeout -- dropdown animation settling
- await page.waitForTimeout(100);
- await optionLocator.click({ timeout: 10000 });
+ // Scope the popup to THIS control via aria-controls (react-aria sets it
+ // while expanded). Popovers portal to , so a global
+ // [role="listbox"]:visible could match a popup left open by a previous
+ // interaction (MultiSelect keeps its popup open by design). The popup can
+ // also close and reopen under a new id while the builder re-renders, so
+ // re-resolve it (and reopen if needed) on every retry.
+ const control = comboboxInput.or(triggerButton).first();
+ await expect(async () => {
+ if ((await control.getAttribute('aria-expanded')) !== 'true') {
+ await control.press('ArrowDown');
+ }
+ const listboxId = await control.getAttribute('aria-controls');
+ if (!listboxId) {
+ throw new Error('Combobox popup did not open (aria-controls not set)');
+ }
+ const option = page
+ .locator(`[role="listbox"][id="${listboxId}"]`)
+ .getByRole('option', { name: optionTitle, exact: true })
+ .first();
+ if (isSearchable && (await option.count()) === 0) {
+ await comboboxInput.fill('');
+ await comboboxInput.fill(optionTitle);
+ throw new Error(`Option "${optionTitle}" not present yet; re-searched`);
+ }
+ await option.click({ timeout: 2000 });
+ }).toPass({ timeout: 30000 });
+
+ // Close the popup if the click didn't: re-selecting the current value emits
+ // no selection change (so the popup stays open) and MultiSelect popups stay
+ // open by design — either would pollute the next interaction's locators.
+ // The control itself may be GONE by now (selecting a field can morph the
+ // whole rule row), which also unmounts its popup — tolerate that.
+ const openListboxId = await control
+ .getAttribute('aria-controls', { timeout: 1000 })
+ .catch(() => null);
+ if (openListboxId) {
+ const openListbox = page.locator(`[role="listbox"][id="${openListboxId}"]`);
+ await openListbox
+ .waitFor({ state: 'hidden', timeout: 2000 })
+ .catch(async () => {
+ // Blur the control — react-aria comboboxes close their popup when
+ // focus leaves. NEVER send Escape here: surrounding antd modals and
+ // forms handle Escape in the capture phase and dismiss themselves.
+ await control.blur({ timeout: 1000 }).catch(() => undefined);
+ await openListbox
+ .waitFor({ state: 'hidden', timeout: 1000 })
+ .catch(() => undefined);
+ });
+ }
};
export const selectRange = async (
@@ -228,16 +262,14 @@ export const selectRange = async (
startDate: string,
endDate: string
) => {
- await ruleLocator.locator('.rule--value .ant-picker-range').click();
-
- await page.locator('.ant-picker-dropdown-range').waitFor({
- state: 'visible',
- });
-
- await page.locator('.ant-picker-input-active input').fill(startDate);
- await page.press('.ant-picker-input-active input', 'Enter');
- await page.locator('.ant-picker-input-active input').fill(endDate);
- await page.press('.ant-picker-input-active input', 'Enter');
+ await ruleLocator
+ .locator('.rule--value input[type="date"]')
+ .nth(0)
+ .fill(startDate);
+ await ruleLocator
+ .locator('.rule--value input[type="date"]')
+ .nth(1)
+ .fill(endDate);
};
export const fillRule = async (
@@ -260,19 +292,10 @@ export const fillRule = async (
const ruleLocator = page.locator('.rule').nth(index - 1);
// Perform click on rule field
- await selectOption(
- page,
- ruleLocator.locator('.rule--field .ant-select'),
- field.id,
- true
- );
+ await selectOption(page, ruleLocator.locator('.rule--field'), field.id, true);
// Perform click on operator
- await selectOption(
- page,
- ruleLocator.locator('.rule--operator .ant-select'),
- condition
- );
+ await selectOption(page, ruleLocator.locator('.rule--operator'), condition);
if (searchCriteria) {
const inputElement = ruleLocator.locator(
@@ -284,47 +307,58 @@ export const fillRule = async (
await inputElement.fill(searchData);
} else {
const dropdownInput = ruleLocator.locator(
- '.widget--widget > .ant-select > .ant-select-selector input'
- );
-
- const aggregateRes1 = page.waitForResponse('/api/v1/search/aggregate?*');
-
- await dropdownInput.click();
-
- await aggregateRes1;
-
- const aggregateRes2 = page.waitForResponse(
- `/api/v1/search/aggregate?*${getEncodedFqn(
- escapeESReservedCharacters(searchData)
- )}*`
+ '.widget--widget input[role="combobox"]'
);
- await dropdownInput.fill(searchData);
-
- await aggregateRes2;
+ const countMatchingOptions = async () => {
+ const listboxId = await dropdownInput.getAttribute('aria-controls');
+ if (!listboxId) {
+ return 0;
+ }
- const dropdown = page.locator('.ant-select-dropdown:visible');
- const exactTitleMatch = dropdown
- .locator('[title]')
- .filter({
- hasText: new RegExp(`^${escapeRegex(searchData)}$`, 'i'),
- })
- .first();
- const partialTextMatch = dropdown
- .locator('.ant-select-item-option-content')
- .filter({
- hasText: new RegExp(escapeRegex(searchData), 'i'),
+ return page
+ .locator(`[role="listbox"][id="${listboxId}"]`)
+ .getByRole('option')
+ .filter({ hasText: new RegExp(escapeRegex(searchData), 'i') })
+ .count();
+ };
+
+ await expect
+ .poll(
+ async () => {
+ await dropdownInput.fill('');
+ await dropdownInput.fill(searchData);
+
+ await page
+ .waitForResponse(
+ (response) =>
+ response.url().includes('/api/v1/search/aggregate'),
+ { timeout: 5_000 }
+ )
+ .catch(() => null);
+
+ return countMatchingOptions();
+ },
+ { timeout: 30_000, intervals: [1_000, 2_000, 3_000] }
+ )
+ .toBeGreaterThan(0);
+
+ const listboxId = await dropdownInput.getAttribute('aria-controls');
+ const dropdown = page.locator(`[role="listbox"][id="${listboxId}"]`);
+ const exactMatch = dropdown
+ .getByRole('option', {
+ name: new RegExp(`^${escapeRegex(searchData)}$`, 'i'),
})
.first();
- if (await exactTitleMatch.count()) {
- await exactTitleMatch.click();
- } else if (await partialTextMatch.count()) {
- await partialTextMatch.click();
+ if (await exactMatch.count()) {
+ await exactMatch.click();
} else {
- // Some suggestion backends normalize or delay option text; Enter keeps
- // the typed criteria and avoids waiting forever on an exact title match.
- await dropdownInput.press('Enter');
+ await dropdown
+ .getByRole('option')
+ .filter({ hasText: new RegExp(escapeRegex(searchData), 'i') })
+ .first()
+ .click();
}
}
@@ -579,9 +613,11 @@ export const checkAddRuleOrGroupWithOperator = async (
});
if (operator === 'OR') {
+ // Conjunction toggle is a react-aria ToggleButtonGroup (selectionMode
+ // "single"), which exposes role="radio" items — not buttons.
await page
.getByTestId('advanced-search-modal')
- .getByRole('button', { name: 'Or' })
+ .getByRole('radio', { name: 'Or' })
.click();
}
@@ -664,28 +700,39 @@ export const runRuleGroupTestsWithNonExistingValue = async (page: Page) => {
// Perform click on rule field
await selectOption(
page,
- ruleLocator.locator('.rule--field .ant-select'),
+ ruleLocator.locator('.rule--field'),
'Database',
true
);
- await selectOption(
- page,
- ruleLocator.locator('.rule--operator .ant-select'),
- '=='
- );
+ await selectOption(page, ruleLocator.locator('.rule--operator'), '==');
const inputElement = ruleLocator.locator(
- '.rule--widget--SELECT .ant-select-selection-search-input'
+ '.rule--widget--SELECT input[role="combobox"]'
);
+
await inputElement.fill('non-existing-value');
- const dropdownText = page.locator('.ant-select-item-empty');
+ await inputElement.press('ArrowDown');
+
+ // Scope to this input's own popup — a popup from a previous step may
+ // still be visible, which would break a global :visible locator.
+ let listboxId: string | null = null;
+ await expect(async () => {
+ listboxId = await inputElement.getAttribute('aria-controls');
+ if (!listboxId) {
+ throw new Error('Combobox popup did not open (aria-controls not set)');
+ }
+ }).toPass({ timeout: 15000 });
+
+ const listbox = page.locator(`[role="listbox"][id="${listboxId}"]`);
- await expect(dropdownText).toContainText('Loading...');
+ await expect(listbox).toBeVisible();
// eslint-disable-next-line playwright/no-wait-for-timeout -- search debounce delay
await page.waitForTimeout(1000);
- await expect(dropdownText).not.toContainText('Loading...');
+ // allowsEmptyCollection keeps the popup open and renders the "No data"
+ // empty state (as an option row) instead of an empty listbox.
+ await expect(listbox.getByText('No data')).toBeVisible();
};
// For fields backed by hard-coded listValues (no aggregate API call), options are
@@ -709,20 +756,12 @@ export const fillStaticListRule = async (
await selectOption(
page,
- ruleLocator.locator('.rule--field .ant-select'),
+ ruleLocator.locator('.rule--field'),
fieldLabel,
true
);
- await selectOption(
- page,
- ruleLocator.locator('.rule--operator .ant-select'),
- condition
- );
- await selectOption(
- page,
- ruleLocator.locator('.widget--widget > .ant-select'),
- value
- );
+ await selectOption(page, ruleLocator.locator('.rule--operator'), condition);
+ await selectOption(page, ruleLocator.locator('.widget--widget'), value);
};
export const getFieldsSuggestionSearchText = (
diff --git a/openmetadata-ui/src/main/resources/ui/playwright/utils/customProperty.ts b/openmetadata-ui/src/main/resources/ui/playwright/utils/customProperty.ts
index 9091264d922e..4edefb04cfb2 100644
--- a/openmetadata-ui/src/main/resources/ui/playwright/utils/customProperty.ts
+++ b/openmetadata-ui/src/main/resources/ui/playwright/utils/customProperty.ts
@@ -949,7 +949,7 @@ export const verifyCustomPropertyInAdvancedSearch = async (
// Select "Custom Properties" from the field dropdown
await selectOption(
page,
- ruleLocator.locator('.rule--field .ant-select'),
+ ruleLocator.locator('.rule--field'),
'Custom Properties',
true
);
@@ -957,7 +957,7 @@ export const verifyCustomPropertyInAdvancedSearch = async (
if (entityType !== 'TableColumn') {
await selectOption(
page,
- ruleLocator.locator('.rule--field .ant-select'),
+ ruleLocator.locator('.rule--field'),
entityType,
true
);
@@ -965,26 +965,26 @@ export const verifyCustomPropertyInAdvancedSearch = async (
if (propertyType === 'Time Interval') {
await selectOption(
page,
- ruleLocator.locator('.rule--field .ant-select'),
+ ruleLocator.locator('.rule--field'),
`${propertyName} (Start)`,
true
);
await selectOption(
page,
- ruleLocator.locator('.rule--field .ant-select'),
+ ruleLocator.locator('.rule--field'),
`${propertyName} (End)`,
true
);
} else if (propertyType === 'Hyperlink') {
await selectOption(
page,
- ruleLocator.locator('.rule--field .ant-select'),
+ ruleLocator.locator('.rule--field'),
`${propertyName} URL`,
true
);
await selectOption(
page,
- ruleLocator.locator('.rule--field .ant-select'),
+ ruleLocator.locator('.rule--field'),
`${propertyName} Display Text`,
true
);
@@ -992,7 +992,7 @@ export const verifyCustomPropertyInAdvancedSearch = async (
for (const column of propertyConfig ?? []) {
await selectOption(
page,
- ruleLocator.locator('.rule--field .ant-select'),
+ ruleLocator.locator('.rule--field'),
`${propertyName} - ${column}`,
true
);
@@ -1000,7 +1000,7 @@ export const verifyCustomPropertyInAdvancedSearch = async (
} else {
await selectOption(
page,
- ruleLocator.locator('.rule--field .ant-select'),
+ ruleLocator.locator('.rule--field'),
propertyName,
true
);
diff --git a/openmetadata-ui/src/main/resources/ui/playwright/utils/customPropertyAdvancedSearchUtils.ts b/openmetadata-ui/src/main/resources/ui/playwright/utils/customPropertyAdvancedSearchUtils.ts
index fce8fcc3f64c..5f1457188597 100644
--- a/openmetadata-ui/src/main/resources/ui/playwright/utils/customPropertyAdvancedSearchUtils.ts
+++ b/openmetadata-ui/src/main/resources/ui/playwright/utils/customPropertyAdvancedSearchUtils.ts
@@ -400,6 +400,55 @@ export const getOperatorLabel = (operator: string): string => {
return operatorMap[operator] || operator;
};
+// The query builder renders native date/datetime inputs, which only accept
+// ISO-like fill values — convert from the CP display format
+// (dd-MM-yyyy[ HH:mm:ss]). Detection is pattern-based because several call
+// sites don't pass propertyType.
+const DATE_DISPLAY_PATTERN = /^(\d{2})-(\d{2})-(\d{4})$/;
+const DATE_TIME_DISPLAY_PATTERN =
+ /^(\d{2})-(\d{2})-(\d{4}) (\d{2}:\d{2}:\d{2})$/;
+
+const toNativeDateInputValue = (value: string | number): string => {
+ const stringValue = String(value);
+ let result = stringValue;
+
+ const dateTimeMatch = stringValue.match(DATE_TIME_DISPLAY_PATTERN);
+ const dateMatch = stringValue.match(DATE_DISPLAY_PATTERN);
+
+ if (dateTimeMatch) {
+ const [, day, month, year, time] = dateTimeMatch;
+ result = `${year}-${month}-${day}T${time}`;
+ } else if (dateMatch) {
+ const [, day, month, year] = dateMatch;
+ result = `${year}-${month}-${day}`;
+ }
+
+ return result;
+};
+
+// Playwright's fill() rejects datetime-local values that carry seconds even
+// when the input has step=1, so converted date values are written through the
+// native value setter (fill() is used for everything else).
+const fillPropertyValue = async (
+ input: ReturnType
,
+ value: string | number
+) => {
+ const nativeValue = toNativeDateInputValue(value);
+
+ if (nativeValue === String(value)) {
+ await input.fill(String(value));
+ } else {
+ await input.evaluate((element, val) => {
+ const setter = Object.getOwnPropertyDescriptor(
+ window.HTMLInputElement.prototype,
+ 'value'
+ )?.set;
+ setter?.call(element, val);
+ element.dispatchEvent(new Event('input', { bubbles: true }));
+ }, nativeValue);
+ }
+};
+
const handlePropertyValueInput = async (
page: Page,
ruleLocator: ReturnType,
@@ -413,7 +462,7 @@ const handlePropertyValueInput = async (
// Fill the input only if it's visible
if (await inputElement.isVisible()) {
// Convert object values to JSON strings
- const stringValue = isObject(value) ? JSON.stringify(value) : String(value);
+ const stringValue = isObject(value) ? JSON.stringify(value) : value;
const apiResponsePromise = isEntityRefProperty
? page.waitForResponse('/api/v1/search/aggregate?*value=.%2A*')
@@ -425,11 +474,15 @@ const handlePropertyValueInput = async (
await apiResponsePromise;
}
- await inputElement.fill(stringValue);
+ await fillPropertyValue(inputElement, stringValue);
- // Press Enter for multiselect operators and date types
- if (
- MULTISELECT_OPERATORS.includes(operator) ||
+ if (MULTISELECT_OPERATORS.includes(operator)) {
+ await page
+ .locator('[role="listbox"]:visible')
+ .getByRole('option', { name: String(value), exact: true })
+ .first()
+ .click();
+ } else if (
((operator === 'equal' || operator === 'not_equal') &&
propertyType === 'dateTime-cp') ||
propertyType === 'date-cp'
@@ -440,7 +493,8 @@ const handlePropertyValueInput = async (
// Handle entity reference selection
if (isEntityRefProperty) {
await page
- .locator(`.ant-select-dropdown:visible [title*="${value as string}"]`)
+ .locator('[role="listbox"]:visible [role="option"]')
+ .filter({ hasText: value as string })
.first()
.click();
}
@@ -459,21 +513,21 @@ export const applyCustomPropertyFilter = async (
await selectOption(
page,
- ruleLocator.locator('.rule--field .ant-select'),
+ ruleLocator.locator('.rule--field'),
'Custom Properties',
true
);
await selectOption(
page,
- ruleLocator.locator('.rule--field .ant-select'),
+ ruleLocator.locator('.rule--field'),
entityType,
true
);
await selectOption(
page,
- ruleLocator.locator('.rule--field .ant-select'),
+ ruleLocator.locator('.rule--field'),
propertyName,
true
);
@@ -481,7 +535,7 @@ export const applyCustomPropertyFilter = async (
const operatorLabel = getOperatorLabel(operator);
await selectOption(
page,
- ruleLocator.locator('.rule--operator .ant-select'),
+ ruleLocator.locator('.rule--operator'),
operatorLabel
);
@@ -495,9 +549,9 @@ export const applyCustomPropertyFilter = async (
const endInput = ruleLocator.locator('.rule--value input').last();
await startInput.click();
- await startInput.fill(String(rangeValue.start));
+ await fillPropertyValue(startInput, rangeValue.start);
await endInput.click();
- await endInput.fill(String(rangeValue.end));
+ await fillPropertyValue(endInput, rangeValue.end);
await page.keyboard.press('Enter');
} else {
@@ -531,28 +585,37 @@ export const verifySearchResults = async (
if (shouldBeVisible) {
if (!(await dashboardCard.isVisible())) {
- await expect
- .poll(
- async () => {
- const retryResponse = await page.request.get(response.url());
-
- if (!retryResponse.ok()) {
- return false;
+ // Re-query the backend through an authenticated context. `page.request`
+ // shares cookies but NOT the app's `Authorization: Bearer ` header,
+ // so re-fetching the search URL with it returns 401 "Token not present"
+ // and the poll would never resolve. getApiContext attaches the token.
+ const { apiContext, afterAction } = await getApiContext(page);
+ try {
+ await expect
+ .poll(
+ async () => {
+ const retryResponse = await apiContext.get(response.url());
+
+ if (!retryResponse.ok()) {
+ return false;
+ }
+
+ const searchData =
+ (await retryResponse.json()) as SearchResponseData;
+
+ return searchData.hits.hits.some(
+ (hit) => hit._source?.fullyQualifiedName === dashboardFQN
+ );
+ },
+ {
+ intervals: [1000, 2000, 5000],
+ timeout: 30000,
}
-
- const searchData =
- (await retryResponse.json()) as SearchResponseData;
-
- return searchData.hits.hits.some(
- (hit) => hit._source?.fullyQualifiedName === dashboardFQN
- );
- },
- {
- intervals: [1000, 2000, 5000],
- timeout: 30000,
- }
- )
- .toBe(true);
+ )
+ .toBe(true);
+ } finally {
+ await afterAction();
+ }
await showAdvancedSearchDialog(page);
const retrySearchResponse = page.waitForResponse(searchResponsePattern);
diff --git a/openmetadata-ui/src/main/resources/ui/playwright/utils/domain.ts b/openmetadata-ui/src/main/resources/ui/playwright/utils/domain.ts
index 5110e6b90d45..b3f5ee5852f0 100644
--- a/openmetadata-ui/src/main/resources/ui/playwright/utils/domain.ts
+++ b/openmetadata-ui/src/main/resources/ui/playwright/utils/domain.ts
@@ -618,6 +618,25 @@ export const fillDomainForm = async (
.click();
};
+/**
+ * Submit the AddDomain/AddDataProduct drawer via its footer Save button.
+ *
+ * The drawer is a SlideoutMenu whose footer height is coupled to its
+ * (re-rendering) body, so the footer — and the Save button inside it — keeps
+ * shifting while the form settles. A pointer `click()` gates on Playwright's
+ * "stable" actionability check and can spin until the test times out
+ * ("element is not stable" → "element was detached from the DOM"), especially
+ * under CI load. save-btn is a native
);
diff --git a/openmetadata-ui/src/main/resources/ui/src/components/common/QueryBuilderWidgetV1/query-builder-widget-v1.less b/openmetadata-ui/src/main/resources/ui/src/components/common/QueryBuilderWidgetV1/query-builder-widget-v1.less
index 22ded344a604..bf10b8d46aa5 100644
--- a/openmetadata-ui/src/main/resources/ui/src/components/common/QueryBuilderWidgetV1/query-builder-widget-v1.less
+++ b/openmetadata-ui/src/main/resources/ui/src/components/common/QueryBuilderWidgetV1/query-builder-widget-v1.less
@@ -11,18 +11,8 @@
* limitations under the License.
*/
-@import (reference) '../../../styles/variables.less';
-
.query-builder-card {
- background-color: @grey-6;
- .ant-alert-info {
- background-color: @blue-8;
- border-color: @blue-7;
-
- .ant-alert-icon {
- color: @blue-7;
- }
- }
+ background-color: var(--color-bg-secondary);
}
.query-builder-form-field
@@ -37,7 +27,7 @@
position: absolute !important;
margin-top: 0;
right: 0;
- top: -56px; // updating this as size of button is increased
+ top: -56px;
display: block;
}
}
@@ -50,12 +40,6 @@
}
.query-builder-form-field {
- .ant-select-disabled.ant-select:not(.ant-select-customize-input)
- .ant-select-selector {
- color: @black;
- background-color: @background-color;
- }
-
.hide--line.one--child {
margin-top: 0;
padding-top: var(--om-space-16);
@@ -75,10 +59,6 @@
.group--field {
width: 180px;
- .ant-select {
- width: 100% !important;
- }
-
label {
font-weight: var(--om-font-weight-regular);
margin-bottom: var(--om-space-6);
@@ -91,7 +71,7 @@
.rule.group-or-rule {
.rule--header {
- .ant-btn-group {
+ .rule--btn-group {
margin: 0 !important;
align-self: flex-start;
}
@@ -106,10 +86,6 @@
.group--field {
margin: 0;
flex: 0 1 25%;
-
- .ant-select {
- min-width: 100% !important; // override the inline min-width style of the select provided by antd
- }
}
}
@@ -129,10 +105,6 @@
display: none;
}
- .rule-container .ant-btn-group {
- visibility: visible;
- }
-
.action.action--ADD-RULE {
position: static !important;
margin-top: var(--om-space-8);
@@ -152,19 +124,11 @@
.widget--widget {
margin: 0;
flex: 1;
-
- .ant-col {
- padding: 0 !important; // remove padding from ant-col inline styling by antd
- }
}
.rule--operator,
.rule--value .rule--widget {
width: 100%;
-
- .ant-select {
- min-width: 100% !important; // override the inline min-width style of the select provided by antd
- }
}
}
}
@@ -199,10 +163,6 @@
}
}
}
-
- .rule-container .ant-btn-group {
- visibility: visible;
- }
}
}
@@ -218,47 +178,42 @@
}
.json-logic-field-select {
- .ant-select-item-group {
- padding-left: var(--om-space-8);
+ .item-group {
+ padding-left: 8px;
position: relative;
- color: @text-color;
- font-size: var(--om-font-size-sm);
- background-color: @grey-6;
+ color: var(--color-text-primary);
+ font-size: 14px;
+ background-color: var(--color-bg-secondary);
}
- /* Add vertical line for children */
- .ant-select-item-option-grouped {
+ .item-option-grouped {
position: relative;
- padding-left: var(--om-space-32);
- /* Indentation for child items */
+ padding-left: 32px;
}
- /* Add vertical line before each child */
- .ant-select-item-option-grouped::before {
+ .item-option-grouped::before {
content: '';
position: absolute;
left: 16px;
top: 0;
bottom: 0;
width: 1px;
- background-color: @border-color;
+ background-color: var(--color-border-primary);
}
- /* Adjust line height for last child */
- .ant-select-item-option-grouped:last-child::before {
+ .item-option-grouped:last-child::before {
height: 16px;
bottom: auto;
}
- /* Add horizontal connector for each child */
- .ant-select-item-option-grouped::after {
+ .item-option-grouped::after {
content: '';
position: absolute;
left: 16px;
top: 16px;
width: 10px;
height: 1px;
- background-color: @border-color;
+ background-color: var(--color-border-primary);
}
}
@@ -276,20 +231,33 @@
}
}
-// Persona rule editor (io.collate PersonaAIContext): push the AND/OR conjunction
-// toggle up to the group header (as the workflow / JSONLogic builder does)
-// instead of the centered default that overlaps the condition rows. Scoped to
-// the `.persona-context-rule-builder` wrapper so other elasticsearch query
-// builders (ScopeFilter, DataContract) are unaffected.
+// Persona rule editor (io.collate PersonaAIContext): render the AND/OR
+// conjunction toggle as its own row at the top of the group (as the workflow /
+// JSONLogic builder does) instead of the centered default that overlaps the
+// condition rows. Scoped to the `.persona-context-rule-builder` wrapper so
+// other elasticsearch query builders (ScopeFilter, DataContract) are
+// unaffected.
.persona-context-rule-builder {
.query-builder-card.elasticsearch .query-builder-container {
.group-or-rule-container.group-container
> .group.group-or-rule
> .group--header {
order: 0;
+ // Reserve vertical space below the toggle so it never sits on top of the
+ // first condition's field label/input.
+ margin-bottom: var(--om-space-12);
.group--conjunctions {
- top: -75px;
+ // The old `top: -75px` targeted a statically-positioned box and did
+ // nothing — the toggle stayed crammed on top of the first rule where a
+ // sibling