diff --git a/.github/playwright/impact-map.generated.json b/.github/playwright/impact-map.generated.json index e54aa728c230..e1d881449497 100644 --- a/.github/playwright/impact-map.generated.json +++ b/.github/playwright/impact-map.generated.json @@ -6971,16 +6971,6 @@ "playwright/e2e/Pages/Lineage/LineageInteraction.spec.ts" ] }, - { - "sources": [ - "openmetadata-ui/src/main/resources/ui/src/components/Entity/EntityLineage/LineageConfigModal.tsx" - ], - "specs": [ - "playwright/e2e/Features/ProfilePlatformSettings.spec.ts", - "playwright/e2e/Flow/LineageSettings.spec.ts", - "playwright/e2e/Pages/Lineage/PlatformLineage.spec.ts" - ] - }, { "sources": [ "openmetadata-ui/src/main/resources/ui/src/components/Entity/EntityLineage/LineageControlButtons/LineageControlButtons.tsx" @@ -7252,22 +7242,11 @@ "openmetadata-ui/src/main/resources/ui/src/components/GlobalSearchBar/GlobalSearchBar.tsx" ], "specs": [ - "playwright/e2e/Features/DomainFilterQueryFilter.spec.ts", - "playwright/e2e/Features/ExploreFilterComposition.spec.ts", "playwright/e2e/Features/ExploreQuickFilters.spec.ts", - "playwright/e2e/Features/ExploreUrlState.spec.ts", - "playwright/e2e/Features/GlobalSearchSuggestions.spec.ts", - "playwright/e2e/Features/SearchIndexNestedColumns.spec.ts", - "playwright/e2e/Flow/ExploreAggregationCountsMatching.spec.ts", "playwright/e2e/Flow/ExploreDiscovery.spec.ts", "playwright/e2e/Flow/GlobalSearch.spec.ts", "playwright/e2e/Flow/MetricSearch.spec.ts", "playwright/e2e/Flow/Navbar.spec.ts", - "playwright/e2e/Flow/Tour.spec.ts", - "playwright/e2e/Pages/CustomProperties.spec.ts", - "playwright/e2e/Pages/ExploreBrowse.spec.ts", - "playwright/e2e/Pages/ExploreTree.spec.ts", - "playwright/e2e/Search/SearchNightly.spec.ts", "playwright/e2e/Search/SearchRelevance.spec.ts" ] }, @@ -7656,14 +7635,6 @@ "playwright/e2e/Pages/Tag.spec.ts" ] }, - { - "sources": [ - "openmetadata-ui/src/main/resources/ui/src/components/Modals/ModalWithFunctionEditor/ModalWithFunctionEditor.tsx" - ], - "specs": [ - "playwright/e2e/Pages/Lineage/LineageInteraction.spec.ts" - ] - }, { "sources": [ "openmetadata-ui/src/main/resources/ui/src/components/Modals/ModalWithMarkdownEditor/ModalWithMarkdownEditor.tsx" @@ -8185,64 +8156,13 @@ "playwright/e2e/Pages/SearchSettings.spec.ts" ] }, - { - "sources": [ - "openmetadata-ui/src/main/resources/ui/src/components/SearchSettings/FieldValueBoostModal/FieldValueBoostModal.tsx" - ], - "specs": [ - "playwright/e2e/Pages/SearchSettings.spec.ts" - ] - }, - { - "sources": [ - "openmetadata-ui/src/main/resources/ui/src/components/SearchSettings/GlobalSettingsItem/GlobalSettingsItem.tsx" - ], - "specs": [ - "playwright/e2e/Pages/Domains.spec.ts", - "playwright/e2e/Pages/SearchSettings.spec.ts" - ] - }, { "sources": [ "openmetadata-ui/src/main/resources/ui/src/components/SearchSettings/SearchPreview/SearchPreview.tsx" ], "specs": [ - "playwright/e2e/Features/AIMode/ConnectionServiceDetails.spec.ts", - "playwright/e2e/Features/ColumnSorting.spec.ts", - "playwright/e2e/Features/Container.spec.ts", - "playwright/e2e/Features/DataAssetRulesEnabled.spec.ts", - "playwright/e2e/Features/DataQuality/DataQuality.spec.ts", - "playwright/e2e/Features/DataQuality/TestCaseDetailsPage.spec.ts", - "playwright/e2e/Features/DataQuality/TestCaseSoftDeleteRestore.spec.ts", - "playwright/e2e/Features/Glossary/GlossaryAssets.spec.ts", - "playwright/e2e/Features/ImpactAnalysis.spec.ts", - "playwright/e2e/Features/LandingPageWidgets/DomainDataProductsWidgets.spec.ts", - "playwright/e2e/Features/MetricAssets.spec.ts", - "playwright/e2e/Features/OnlineUsers.spec.ts", - "playwright/e2e/Features/Pagination.spec.ts", "playwright/e2e/Features/ProfilePlatformSettings.spec.ts", - "playwright/e2e/Features/SchemaSearch.spec.ts", - "playwright/e2e/Features/Table.spec.ts", - "playwright/e2e/Flow/PersonaFlow.spec.ts", - "playwright/e2e/Flow/SchemaTable.spec.ts", - "playwright/e2e/Pages/AIProfile/ProfileMembers.spec.ts", - "playwright/e2e/Pages/AIProfile/ProfileTeam.spec.ts", - "playwright/e2e/Pages/Bots.spec.ts", - "playwright/e2e/Pages/BotsProfile.spec.ts", - "playwright/e2e/Pages/DataContracts.spec.ts", - "playwright/e2e/Pages/DataProductAndSubdomains.spec.ts", - "playwright/e2e/Pages/DomainAdvanced.spec.ts", - "playwright/e2e/Pages/DomainUIInteractions.spec.ts", - "playwright/e2e/Pages/Domains.spec.ts", - "playwright/e2e/Pages/InputOutputPorts.spec.ts", "playwright/e2e/Pages/SearchSettings.spec.ts", - "playwright/e2e/Pages/ServiceEntity.spec.ts", - "playwright/e2e/Pages/ServiceListing.spec.ts", - "playwright/e2e/Pages/Teams.spec.ts", - "playwright/e2e/Pages/TestSuite.spec.ts", - "playwright/e2e/Pages/TestSuiteDetailsPage.spec.ts", - "playwright/e2e/Pages/UserDetails.spec.ts", - "playwright/e2e/Pages/Users.spec.ts", "playwright/e2e/Search/SearchRelevance.spec.ts" ] }, @@ -8455,8 +8375,11 @@ "specs": [ "playwright/e2e/Features/AccessControlSettings.spec.ts", "playwright/e2e/Features/IncidentManager.spec.ts", + "playwright/e2e/Features/SSOConfiguration.spec.ts", "playwright/e2e/Features/Tasks/TaskAssigneeManagement.spec.ts", - "playwright/e2e/Pages/CustomProperties.spec.ts" + "playwright/e2e/Flow/PersonaFlow.spec.ts", + "playwright/e2e/Pages/CustomProperties.spec.ts", + "playwright/e2e/Pages/Users.spec.ts" ] }, { @@ -8472,8 +8395,7 @@ "openmetadata-ui/src/main/resources/ui/src/components/Settings/Email/EmailConfigForm/EmailConfigForm.component.tsx" ], "specs": [ - "playwright/e2e/Features/ProfilePlatformSettings.spec.ts", - "playwright/e2e/Pages/EmailConfiguration.spec.ts" + "playwright/e2e/Features/ProfilePlatformSettings.spec.ts" ] }, { @@ -8484,6 +8406,15 @@ "playwright/e2e/Features/ProfilePlatformSettings.spec.ts" ] }, + { + "sources": [ + "openmetadata-ui/src/main/resources/ui/src/components/Settings/Persona/CustomizeUI/CustomizeUI.tsx" + ], + "specs": [ + "playwright/e2e/Features/CustomizeDetailPage.spec.ts", + "playwright/e2e/Pages/DataContracts.spec.ts" + ] + }, { "sources": [ "openmetadata-ui/src/main/resources/ui/src/components/Settings/Services/AddIngestion/AddIngestion.component.tsx" @@ -8719,7 +8650,6 @@ "playwright/e2e/Flow/AddRoleAndAssignToUser.spec.ts", "playwright/e2e/Pages/AIProfile/ProfileMembers.spec.ts", "playwright/e2e/Pages/AIProfile/ProfileUser.spec.ts", - "playwright/e2e/Pages/BotsProfile.spec.ts", "playwright/e2e/Pages/DataContracts.spec.ts" ] }, @@ -10969,6 +10899,14 @@ "playwright/e2e/Pages/SearchSettings.spec.ts" ] }, + { + "sources": [ + "openmetadata-ui/src/main/resources/ui/src/components/discovery/personal-space/Profile/tabs/platform-settings/search/FieldValueBoostDialog.tsx" + ], + "specs": [ + "playwright/e2e/Pages/SearchSettings.spec.ts" + ] + }, { "sources": [ "openmetadata-ui/src/main/resources/ui/src/components/discovery/personal-space/Profile/tabs/platform-settings/search/FieldValueBoostTable.tsx" @@ -12534,14 +12472,6 @@ "playwright/e2e/Pages/DataProducts.spec.ts" ] }, - { - "sources": [ - "openmetadata-ui/src/main/resources/ui/src/pages/Configuration/EditUrlConfiguration/EditUrlConfigurationPage.tsx" - ], - "specs": [ - "playwright/e2e/Pages/OmdURLConfiguration.spec.ts" - ] - }, { "sources": [ "openmetadata-ui/src/main/resources/ui/src/pages/Configuration/LoginConfigurationDetails/LoginConfigurationPage.tsx" @@ -12781,22 +12711,6 @@ "playwright/e2e/Pages/IntakeForm.spec.ts" ] }, - { - "sources": [ - "openmetadata-ui/src/main/resources/ui/src/pages/KPIPage/AddKPIPage.tsx" - ], - "specs": [ - "playwright/e2e/Features/DataQuality/ProfilerSettingsAfterReindex.spec.ts" - ] - }, - { - "sources": [ - "openmetadata-ui/src/main/resources/ui/src/pages/KPIPage/EditKPIPage.tsx" - ], - "specs": [ - "playwright/e2e/Features/DataQuality/ProfilerSettingsAfterReindex.spec.ts" - ] - }, { "sources": [ "openmetadata-ui/src/main/resources/ui/src/pages/KnowledgeCenterFilterPage/KnowledgeCenterFilterPage.tsx" @@ -12810,10 +12724,6 @@ "openmetadata-ui/src/main/resources/ui/src/pages/LearningResourcesPage/LearningResourceForm.component.tsx" ], "specs": [ - "playwright/e2e/Features/IncidentManager.spec.ts", - "playwright/e2e/Features/ProfileGovernance.spec.ts", - "playwright/e2e/Features/ProfilePlatformSettings.spec.ts", - "playwright/e2e/Pages/GlossaryTermRelationSettings.spec.ts", "playwright/e2e/Pages/LearningResources.spec.ts" ] }, @@ -12833,8 +12743,7 @@ "specs": [ "playwright/e2e/Features/MetricHierarchy.spec.ts", "playwright/e2e/Features/ProfilePlatformSettings.spec.ts", - "playwright/e2e/Flow/LineageSettings.spec.ts", - "playwright/e2e/Pages/Lineage/PlatformLineage.spec.ts" + "playwright/e2e/Flow/LineageSettings.spec.ts" ] }, { @@ -12912,8 +12821,7 @@ "openmetadata-ui/src/main/resources/ui/src/pages/PoliciesPage/AddPolicyPage/AddPolicyPage.tsx" ], "specs": [ - "playwright/e2e/Features/AccessControlSettings.spec.ts", - "playwright/e2e/Pages/Policies.spec.ts" + "playwright/e2e/Features/AccessControlSettings.spec.ts" ] }, { @@ -12935,15 +12843,6 @@ "playwright/e2e/Pages/Policies.spec.ts" ] }, - { - "sources": [ - "openmetadata-ui/src/main/resources/ui/src/pages/PoliciesPage/RuleForm/RuleForm.tsx" - ], - "specs": [ - "playwright/e2e/Features/AccessControlSettings.spec.ts", - "playwright/e2e/Pages/Policies.spec.ts" - ] - }, { "sources": [ "openmetadata-ui/src/main/resources/ui/src/pages/ProfilerConfigurationPage/ProfilerConfigurationPage.tsx" @@ -12958,25 +12857,6 @@ "openmetadata-ui/src/main/resources/ui/src/pages/RolesPage/AddAttributeModal/AddAttributeModal.tsx" ], "specs": [ - "playwright/e2e/Features/ContextCenterDocument.spec.ts", - "playwright/e2e/Features/ContextCenterMemories.spec.ts", - "playwright/e2e/Features/DataQuality/CertificationFilter.spec.ts", - "playwright/e2e/Features/DataQuality/DataObservabilityGovernanceTab.spec.ts", - "playwright/e2e/Features/DataQuality/DataQuality.spec.ts", - "playwright/e2e/Features/DataQuality/DataQualityDashboard.spec.ts", - "playwright/e2e/Features/DomainFilterQueryFilter.spec.ts", - "playwright/e2e/Features/ExploreFilterComposition.spec.ts", - "playwright/e2e/Features/ExploreQuickFilters.spec.ts", - "playwright/e2e/Features/ExploreSortOrderFilter.spec.ts", - "playwright/e2e/Features/ExploreUrlState.spec.ts", - "playwright/e2e/Features/IncidentManager.spec.ts", - "playwright/e2e/Features/NestedColumnsExpandCollapse.spec.ts", - "playwright/e2e/Features/SearchExport.spec.ts", - "playwright/e2e/Flow/ExploreDiscovery.spec.ts", - "playwright/e2e/Pages/AuditLogs.spec.ts", - "playwright/e2e/Pages/DataContracts.spec.ts", - "playwright/e2e/Pages/DataProductCertificationFilter.spec.ts", - "playwright/e2e/Pages/Lineage/LineageFilters.spec.ts", "playwright/e2e/Pages/Roles.spec.ts", "playwright/e2e/Pages/Tags.spec.ts" ] @@ -12987,20 +12867,8 @@ ], "specs": [ "playwright/e2e/Features/AccessControlSettings.spec.ts", - "playwright/e2e/Features/EntityRenameConsolidation.spec.ts", - "playwright/e2e/Features/Glossary/GlossaryMiscOperations.spec.ts", - "playwright/e2e/Features/Glossary/GlossaryMutualExclusivityDataProductTree.spec.ts", - "playwright/e2e/Features/MetricCustomUnitFlow.spec.ts", - "playwright/e2e/Features/MetricHierarchy.spec.ts", - "playwright/e2e/Features/MultipleRename.spec.ts", "playwright/e2e/Flow/AddRoleAndAssignToUser.spec.ts", - "playwright/e2e/Flow/PersonaFlow.spec.ts", - "playwright/e2e/Pages/AIProfile/ProfileTeam.spec.ts", - "playwright/e2e/Pages/DataProductAndSubdomains.spec.ts", - "playwright/e2e/Pages/EditClassification.spec.ts", - "playwright/e2e/Pages/IntakeForm.spec.ts", "playwright/e2e/Pages/Roles.spec.ts", - "playwright/e2e/Pages/Tags.spec.ts", "playwright/e2e/Pages/Teams.spec.ts" ] }, @@ -13254,30 +13122,6 @@ "playwright/e2e/Pages/GlossaryImportExport.spec.ts" ] }, - { - "sources": [ - "openmetadata-ui/src/main/resources/ui/src/pages/TeamsPage/AddTeamForm.tsx" - ], - "specs": [ - "playwright/e2e/Features/AIMode/Observability.spec.ts", - "playwright/e2e/Features/DataQuality/TestLibrary.spec.ts", - "playwright/e2e/Features/EntityRenameConsolidation.spec.ts", - "playwright/e2e/Features/Glossary/GlossaryMiscOperations.spec.ts", - "playwright/e2e/Features/Glossary/GlossaryMutualExclusivityDataProductTree.spec.ts", - "playwright/e2e/Features/MetricCustomUnitFlow.spec.ts", - "playwright/e2e/Features/MetricHierarchy.spec.ts", - "playwright/e2e/Features/MultipleRename.spec.ts", - "playwright/e2e/Flow/AddRoleAndAssignToUser.spec.ts", - "playwright/e2e/Flow/PersonaFlow.spec.ts", - "playwright/e2e/Pages/AIProfile/ProfileTeam.spec.ts", - "playwright/e2e/Pages/AIProfile/ProfileUser.spec.ts", - "playwright/e2e/Pages/BotsProfile.spec.ts", - "playwright/e2e/Pages/DataProductAndSubdomains.spec.ts", - "playwright/e2e/Pages/EditClassification.spec.ts", - "playwright/e2e/Pages/IntakeForm.spec.ts", - "playwright/e2e/Pages/Tags.spec.ts" - ] - }, { "sources": [ "openmetadata-ui/src/main/resources/ui/src/pages/TestSuiteDetailsPage/TestSuiteDetailsPage.component.tsx" diff --git a/docs/decisions/2026-10-13-classic-forms-reuse-the-ai-mode-core-form.md b/docs/decisions/2026-10-13-classic-forms-reuse-the-ai-mode-core-form.md new file mode 100644 index 000000000000..7a9e612b95bf --- /dev/null +++ b/docs/decisions/2026-10-13-classic-forms-reuse-the-ai-mode-core-form.md @@ -0,0 +1,27 @@ +# Classic settings forms reuse the AI-mode core form when one exists + +- **Status:** Accepted +- **Revisions:** v1 2026-10-13 (initial) +- **Deciders:** Harshit Shah +- **Guard:** reviewer +- **Related:** ADR:2026-10-11-antd-imports-are-frozen-by-eslint-suppressions, ADR:2026-10-11-migrated-antd-components-adopt-core-styling, open-metadata/OpenMetadata#35285 + +## Context +Several classic settings pages (access-control rules, field value boosts, the permission +debugger, custom properties, learning resources) had an antd form while AI mode already had a +`@openmetadata/ui-core-components` version of the same form with the same payload. Migrating the +classic copy to core in place would leave two core forms for one entity, with fields, +validation and payload mapping that drift apart over time. + +## Decision +When a classic form migrates off antd and AI mode already has a core version of that form, the +classic page renders the shared AI component and the antd copy is deleted. If the AI form is +embedded in an AI-only page shell, its fields and payload mapping are extracted into a shared +component, hook or `*.utils.ts` that both pages use (for example `CustomPropertyFormFields`, +`useLearningResourceFields`). A second core copy of the form is not written. + +## Consequences +One place to change a form's fields, validation and payload, at the cost of classic pages +taking the AI form's layout. Classic-only behaviour (a doc side panel, a drawer shell) stays in +the classic page around the shared fields. Revisit if classic and AI forms are meant to diverge +for the same entity. diff --git a/docs/decisions/2026-10-13-react-hook-form-rules-own-hookform-validation.md b/docs/decisions/2026-10-13-react-hook-form-rules-own-hookform-validation.md new file mode 100644 index 000000000000..f9dd74e1b121 --- /dev/null +++ b/docs/decisions/2026-10-13-react-hook-form-rules-own-hookform-validation.md @@ -0,0 +1,27 @@ +# react-hook-form rules are the only validation that blocks a HookForm submit + +- **Status:** Accepted +- **Revisions:** v1 2026-10-13 (initial) +- **Deciders:** Harshit Shah +- **Guard:** `openmetadata-ui-core-components/src/main/resources/ui/src/components/base/form/hook-form.test.tsx` +- **Related:** open-metadata/OpenMetadata#35285 + +## Context +`HookForm` wraps react-aria's `Form`, which validated natively by default. A core field with +`isRequired` rendered a native `required` attribute, so the browser blocked the submit event +before react-hook-form ran: no inline message appeared, and react-hook-form never validated the +form (the KPI and semantics forms showed this). Mixing the two validators also left two sources +of truth for whether a field is required. + +## Decision +`HookForm` sets react-aria `validationBehavior="aria"` by default. Inside a `HookForm`, +react-hook-form `rules` are the only thing that blocks submit; `isRequired` on a core control is +presentational (asterisk and ARIA state). Every required field carries a react-hook-form +`required` rule: `FieldProp.required` adds one automatically for `FormFields`/`getField`, and a +render-prop `FormField` passes it in `rules`. A control bound outside `FormField` (for example +through `form.setValue`) validates in the submit handler or is gated by a disabled submit. + +## Consequences +Errors render inline through `fieldState` in every `HookForm`. A field marked `isRequired` +without a matching rule is not enforced, so reviewers check new required fields for the rule. +A caller that needs native validation passes `validationBehavior="native"` explicitly. diff --git a/openmetadata-ui-core-components/src/main/resources/ui/src/components/application/form-field/form-field.types.ts b/openmetadata-ui-core-components/src/main/resources/ui/src/components/application/form-field/form-field.types.ts index 13f9210ce92a..e9b0713e8c26 100644 --- a/openmetadata-ui-core-components/src/main/resources/ui/src/components/application/form-field/form-field.types.ts +++ b/openmetadata-ui-core-components/src/main/resources/ui/src/components/application/form-field/form-field.types.ts @@ -91,6 +91,8 @@ export interface FieldPropsMap { children?: ReactNode; colors?: string[]; 'data-testid'?: string; + /** TEXT / PASSWORD / NUMBER only: test id on the inner ``, not the wrapper. */ + inputDataTestId?: string; defaultCamera?: 'environment' | 'user'; defaultIcon?: { component: FC }; disabled?: boolean; diff --git a/openmetadata-ui-core-components/src/main/resources/ui/src/components/application/tree-select/tree-select.tsx b/openmetadata-ui-core-components/src/main/resources/ui/src/components/application/tree-select/tree-select.tsx index b4ea29d2a898..d3848f3c9be4 100644 --- a/openmetadata-ui-core-components/src/main/resources/ui/src/components/application/tree-select/tree-select.tsx +++ b/openmetadata-ui-core-components/src/main/resources/ui/src/components/application/tree-select/tree-select.tsx @@ -1148,7 +1148,7 @@ export const TreeSelect = ({ multiple && selectedData.map((node) => (

{node.label} diff --git a/openmetadata-ui-core-components/src/main/resources/ui/src/components/base/autocomplete/autocomplete.tsx b/openmetadata-ui-core-components/src/main/resources/ui/src/components/base/autocomplete/autocomplete.tsx index 009feba5fb45..ae2ec8dcf53d 100644 --- a/openmetadata-ui-core-components/src/main/resources/ui/src/components/base/autocomplete/autocomplete.tsx +++ b/openmetadata-ui-core-components/src/main/resources/ui/src/components/base/autocomplete/autocomplete.tsx @@ -259,13 +259,14 @@ const InnerAutocomplete = ({ : allSelected.length - visibleSelected.length; return ( -

+
{!isSelectionEmpty && visibleSelected.map((item) => context.renderTag ? ( context.renderTag(item, () => context.onRemove(new Set([item.id]))) ) : ( void }) => { + const form = useForm({ defaultValues: { name: '' } }); + + return ( + + + {({ field, fieldState }) => ( + + )} + + + + ); +}; + +describe('HookForm', () => { + it('lets react-hook-form report a required field instead of native validation', async () => { + const onSubmit = vi.fn(); + render(); + + await userEvent.click(screen.getByRole('button', { name: 'Save' })); + + expect(await screen.findByText('Name is required')).toBeInTheDocument(); + expect(onSubmit).not.toHaveBeenCalled(); + }); +}); diff --git a/openmetadata-ui-core-components/src/main/resources/ui/src/components/base/form/hook-form.tsx b/openmetadata-ui-core-components/src/main/resources/ui/src/components/base/form/hook-form.tsx index 4f8c967890a9..0164dcbe398e 100644 --- a/openmetadata-ui-core-components/src/main/resources/ui/src/components/base/form/hook-form.tsx +++ b/openmetadata-ui-core-components/src/main/resources/ui/src/components/base/form/hook-form.tsx @@ -118,6 +118,8 @@ export const HookForm = ({ `formClassName` when their form needs more room than the default. */} ({
) : ( <> - + {showFieldDocs ? ( +
{!isSelectionEmpty && comboBoxContext?.selectedItems?.items?.map((value) => ( diff --git a/openmetadata-ui-core-components/src/main/resources/ui/src/components/base/toggle/toggle.tsx b/openmetadata-ui-core-components/src/main/resources/ui/src/components/base/toggle/toggle.tsx index 48b2492b99e8..a0fde53ec074 100644 --- a/openmetadata-ui-core-components/src/main/resources/ui/src/components/base/toggle/toggle.tsx +++ b/openmetadata-ui-core-components/src/main/resources/ui/src/components/base/toggle/toggle.tsx @@ -54,7 +54,7 @@ export const ToggleBase = ({ // `tw:relative` anchors the slim variant's ::after border — the element's own // outline is reserved for the focus ring below. The knob uses translate-x, not // absolute positioning, so this does not affect it. - 'tw:relative tw:cursor-pointer tw:rounded-full tw:bg-tertiary tw:outline-focus-ring tw:transition tw:duration-150 tw:ease-linear', + 'tw:relative tw:shrink-0 tw:cursor-pointer tw:rounded-full tw:bg-tertiary tw:outline-focus-ring tw:transition tw:duration-150 tw:ease-linear', // Dark bg-tertiary equals bg-surface, so the off track would vanish on cards. !isSelected && !isDisabled && 'tw:dark:bg-quaternary', isSelected && 'tw:bg-brand-solid', @@ -127,7 +127,8 @@ export const Toggle = ({ {...ariaSwitchProps} className={(renderProps) => cx( - 'tw:flex tw:w-max tw:items-start', + // max-w-full lets a long hint wrap inside its container instead of overflowing it. + 'tw:flex tw:w-max tw:max-w-full tw:items-start', renderProps.isDisabled && 'tw:cursor-not-allowed', sizes[size].root, typeof className === 'function' ? className(renderProps) : className diff --git a/openmetadata-ui/src/main/resources/ui/eslint-rules/openmetadata-antd.test.mjs b/openmetadata-ui/src/main/resources/ui/eslint-rules/openmetadata-antd.test.mjs index 9475d6506ab7..b6186fa5ac91 100644 --- a/openmetadata-ui/src/main/resources/ui/eslint-rules/openmetadata-antd.test.mjs +++ b/openmetadata-ui/src/main/resources/ui/eslint-rules/openmetadata-antd.test.mjs @@ -163,7 +163,7 @@ test('the app config never suppresses a migrated component', async () => { // Exact, so re-running --suppress-rule cannot quietly absorb new antd usage. // Lower this when a PR removes antd imports and prunes the baseline. - const EXPECTED_SUPPRESSED_ANTD_SYMBOLS = 409; + const EXPECTED_SUPPRESSED_ANTD_SYMBOLS = 246; const suppressedSymbols = Object.values(suppressions).reduce( (total, rules) => total + (rules['openmetadata-antd/no-antd-import']?.count ?? 0), diff --git a/openmetadata-ui/src/main/resources/ui/eslint-suppressions.json b/openmetadata-ui/src/main/resources/ui/eslint-suppressions.json index 40e64d7969b0..3d8bd016f676 100644 --- a/openmetadata-ui/src/main/resources/ui/eslint-suppressions.json +++ b/openmetadata-ui/src/main/resources/ui/eslint-suppressions.json @@ -534,7 +534,7 @@ }, "playwright/e2e/Pages/Policies.spec.ts": { "om-playwright/no-positional-locator": { - "count": 10 + "count": 8 } }, "playwright/e2e/Pages/Roles.spec.ts": { @@ -560,11 +560,6 @@ "count": 3 } }, - "playwright/e2e/Pages/TaskFormSettings.spec.ts": { - "om-playwright/no-positional-locator": { - "count": 1 - } - }, "playwright/e2e/Pages/TasksUIFlow.spec.ts": { "om-playwright/no-positional-locator": { "count": 5 @@ -750,11 +745,6 @@ "count": 3 } }, - "playwright/utils/navbar.ts": { - "om-playwright/no-positional-locator": { - "count": 2 - } - }, "playwright/utils/reviewerWorkflow.utils.ts": { "om-playwright/no-positional-locator": { "count": 1 @@ -835,7 +825,7 @@ }, "src/components/DataAssetRules/DataAssetRules.component.tsx": { "openmetadata-antd/no-antd-import": { - "count": 5 + "count": 2 } }, "src/components/DataInsight/DataInsightProgressBar.tsx": { @@ -873,36 +863,11 @@ "count": 3 } }, - "src/components/Database/SampleDataTable/SampleDataTable.component.tsx": { - "openmetadata-antd/no-antd-import": { - "count": 1 - } - }, "src/components/Database/SampleDataWithMessages/MessageCard.tsx": { "openmetadata-antd/no-antd-import": { "count": 1 } }, - "src/components/Database/SchemaTable/SchemaTable.component.tsx": { - "openmetadata-antd/no-antd-import": { - "count": 1 - } - }, - "src/components/Database/TableQueries/QueryCard.tsx": { - "openmetadata-antd/no-antd-import": { - "count": 1 - } - }, - "src/components/Database/TableQueries/QueryUsedByOtherTable/QueryUsedByOtherTable.component.tsx": { - "openmetadata-antd/no-antd-import": { - "count": 1 - } - }, - "src/components/Database/TableQueries/TableQueries.interface.ts": { - "openmetadata-antd/no-antd-import": { - "count": 1 - } - }, "src/components/Database/TableQueries/TableQueries.tsx": { "openmetadata-antd/no-antd-import": { "count": 3 @@ -918,11 +883,6 @@ "count": 1 } }, - "src/components/Entity/EntityLineage/AppPipelineModel/AddPipeLineModal.tsx": { - "openmetadata-antd/no-antd-import": { - "count": 1 - } - }, "src/components/Entity/EntityLineage/EdgeInteractionOverlay.component.tsx": { "openmetadata-antd/no-antd-import": { "count": 1 @@ -933,26 +893,6 @@ "count": 1 } }, - "src/components/Entity/EntityLineage/LineageConfigModal.tsx": { - "openmetadata-antd/no-antd-import": { - "count": 2 - } - }, - "src/components/Entity/EntityLineage/LineageSearchSelect/LineageSearchSelect.tsx": { - "openmetadata-antd/no-antd-import": { - "count": 3 - } - }, - "src/components/Entity/EntityLineage/NodeChildren/NodeChildren.component.tsx": { - "openmetadata-antd/no-antd-import": { - "count": 2 - } - }, - "src/components/Entity/EntityLineage/NodeSuggestions.component.tsx": { - "openmetadata-antd/no-antd-import": { - "count": 1 - } - }, "src/components/Explore/EntitySummaryPanel/APIEndpointSummary/APIEndpointSummary.tsx": { "openmetadata-antd/no-antd-import": { "count": 2 @@ -970,7 +910,7 @@ }, "src/components/GlobalSearchBar/GlobalSearchBar.tsx": { "openmetadata-antd/no-antd-import": { - "count": 3 + "count": 1 } }, "src/components/KnowledgeCenter/KnowledgePageVersion/KnowledgePageVersion.tsx": { @@ -993,21 +933,6 @@ "count": 1 } }, - "src/components/Modals/ModalWithFunctionEditor/ModalWithFunctionEditor.tsx": { - "openmetadata-antd/no-antd-import": { - "count": 4 - } - }, - "src/components/Modals/ModalWithQueryEditor/ModalWithQueryEditor.tsx": { - "openmetadata-antd/no-antd-import": { - "count": 3 - } - }, - "src/components/Modals/ProfileEditModal/ProfileEditModal.tsx": { - "openmetadata-antd/no-antd-import": { - "count": 3 - } - }, "src/components/Modals/TourEndModal/TourEndModal.tsx": { "openmetadata-antd/no-antd-import": { "count": 1 @@ -1033,19 +958,9 @@ "count": 1 } }, - "src/components/MyData/Persona/AddEditPersona/AddEditPersona.component.tsx": { - "openmetadata-antd/no-antd-import": { - "count": 3 - } - }, - "src/components/MyData/Persona/PersonaSelectableList/PersonaSelectableList.component.tsx": { - "openmetadata-antd/no-antd-import": { - "count": 2 - } - }, "src/components/NavBar/NavBar.tsx": { "openmetadata-antd/no-antd-import": { - "count": 4 + "count": 3 } }, "src/components/NotificationBox/NotificationBox.component.tsx": { @@ -1095,12 +1010,12 @@ }, "src/components/SearchSettings/EntitySeachSettings/EntitySearchSettings.tsx": { "openmetadata-antd/no-antd-import": { - "count": 4 + "count": 3 } }, "src/components/SearchSettings/FieldConfiguration/FieldConfiguration.tsx": { "openmetadata-antd/no-antd-import": { - "count": 5 + "count": 4 } }, "src/components/SearchSettings/FieldValueBoostList/FieldValueBoostList.tsx": { @@ -1108,24 +1023,9 @@ "count": 1 } }, - "src/components/SearchSettings/FieldValueBoostModal/FieldValueBoostModal.tsx": { - "openmetadata-antd/no-antd-import": { - "count": 5 - } - }, "src/components/SearchSettings/GlobalSettingsItem/GlobalSettingsItem.tsx": { "openmetadata-antd/no-antd-import": { - "count": 2 - } - }, - "src/components/SearchSettings/SearchPreview/SearchPreview.tsx": { - "openmetadata-antd/no-antd-import": { - "count": 2 - } - }, - "src/components/SearchSettings/TermBoost/TermBoost.tsx": { - "openmetadata-antd/no-antd-import": { - "count": 3 + "count": 1 } }, "src/components/ServiceInsights/AgentsStatusWidget/AgentsStatusWidget.tsx": { @@ -1160,7 +1060,7 @@ }, "src/components/Settings/Bot/BotDetails/AuthMechanism.tsx": { "openmetadata-antd/no-antd-import": { - "count": 2 + "count": 1 } }, "src/components/Settings/Bot/BotDetails/AuthMechanismForm.test.tsx": { @@ -1168,31 +1068,11 @@ "count": 1 } }, - "src/components/Settings/Bot/BotDetails/AuthMechanismForm.tsx": { - "openmetadata-antd/no-antd-import": { - "count": 3 - } - }, "src/components/Settings/Bot/BotDetails/BotDetails.component.tsx": { - "openmetadata-antd/no-antd-import": { - "count": 3 - } - }, - "src/components/Settings/CustomProperty/AddCustomProperty/AddCustomProperty.tsx": { "openmetadata-antd/no-antd-import": { "count": 2 } }, - "src/components/Settings/Email/EmailConfigForm/EmailConfigForm.component.tsx": { - "openmetadata-antd/no-antd-import": { - "count": 3 - } - }, - "src/components/Settings/Email/TestEmail/TestEmail.component.tsx": { - "openmetadata-antd/no-antd-import": { - "count": 3 - } - }, "src/components/Settings/Services/Ingestion/AddIngestionButton.interface.ts": { "openmetadata-antd/no-antd-import": { "count": 1 @@ -1203,11 +1083,6 @@ "count": 1 } }, - "src/components/Settings/Services/Ingestion/IngestionWorkflowForm/ProfileSampleConfigField.tsx": { - "openmetadata-antd/no-antd-import": { - "count": 1 - } - }, "src/components/Settings/SettingItemCard/SettingItemCard.component.tsx": { "openmetadata-antd/no-antd-import": { "count": 2 @@ -1220,17 +1095,12 @@ }, "src/components/Settings/Team/TeamDetails/TeamsHeaderSection/TeamsHeadingLabel.component.tsx": { "openmetadata-antd/no-antd-import": { - "count": 4 + "count": 3 } }, "src/components/Settings/Team/TeamDetails/TeamsHeaderSection/TeamsInfo.component.tsx": { "openmetadata-antd/no-antd-import": { - "count": 5 - } - }, - "src/components/Settings/Team/TeamDetails/TeamsHeaderSection/TeamsSubscription.component.tsx": { - "openmetadata-antd/no-antd-import": { - "count": 4 + "count": 3 } }, "src/components/Settings/Team/TeamsSelectable/TeamsSelectable.tsx": { @@ -1243,21 +1113,6 @@ "count": 2 } }, - "src/components/Settings/Users/AdminPermissionDebugger/AdminPermissionDebugger.component.tsx": { - "openmetadata-antd/no-antd-import": { - "count": 4 - } - }, - "src/components/Settings/Users/ChangePasswordForm.tsx": { - "openmetadata-antd/no-antd-import": { - "count": 2 - } - }, - "src/components/Settings/Users/CreateUser/CreateUser.component.tsx": { - "openmetadata-antd/no-antd-import": { - "count": 7 - } - }, "src/components/Settings/Users/UserProfileIcon/UserProfileIcon.component.tsx": { "openmetadata-antd/no-antd-import": { "count": 1 @@ -1268,11 +1123,6 @@ "count": 1 } }, - "src/components/Settings/Users/UsersProfile/UserProfileRoles/UserProfileRoles.component.tsx": { - "openmetadata-antd/no-antd-import": { - "count": 1 - } - }, "src/components/SettingsSso/ProviderSelector/ProviderSelector.tsx": { "openmetadata-antd/no-antd-import": { "count": 1 @@ -1283,16 +1133,6 @@ "count": 1 } }, - "src/components/SettingsSso/SSOConfigurationForm/SsoConfigurationFormArrayFieldTemplate.tsx": { - "openmetadata-antd/no-antd-import": { - "count": 1 - } - }, - "src/components/SettingsSso/SSOConfigurationForm/SsoRolesSelectField.tsx": { - "openmetadata-antd/no-antd-import": { - "count": 1 - } - }, "src/components/SettingsSso/SSOGroupedFieldTemplate/SSOGroupedFieldTemplate.tsx": { "openmetadata-antd/no-antd-import": { "count": 2 @@ -1328,16 +1168,6 @@ "count": 1 } }, - "src/components/common/ColorPicker/ColorPicker.component.tsx": { - "openmetadata-antd/no-antd-import": { - "count": 2 - } - }, - "src/components/common/CustomUnitSelect/CustomUnitSelect.tsx": { - "openmetadata-antd/no-antd-import": { - "count": 3 - } - }, "src/components/common/DatePicker/DatePicker.tsx": { "openmetadata-antd/no-antd-import": { "count": 2 @@ -1373,14 +1203,9 @@ "count": 2 } }, - "src/components/common/Form/JSONSchema/JSONSchemaTemplate/WorkflowArrayFieldTemplate.tsx": { - "openmetadata-antd/no-antd-import": { - "count": 1 - } - }, "src/components/common/Form/JSONSchema/JsonSchemaWidgets/LdapRoleMappingWidget/LdapRoleMappingWidget.tsx": { "openmetadata-antd/no-antd-import": { - "count": 3 + "count": 1 } }, "src/components/common/Form/JSONSchema/JsonSchemaWidgets/ManifestJsonWidget/ManifestJsonWidget.tsx": { @@ -1410,7 +1235,7 @@ }, "src/components/common/RolesCard/RolesCard.component.tsx": { "openmetadata-antd/no-antd-import": { - "count": 3 + "count": 2 } }, "src/components/common/RolesElement/RolesElement.component.tsx": { @@ -1443,11 +1268,6 @@ "count": 1 } }, - "src/components/common/TeamTypeSelect/TeamTypeSelect.component.tsx": { - "openmetadata-antd/no-antd-import": { - "count": 3 - } - }, "src/components/common/UserTeamSelectableListSearchInput/UserTeamSelectableListSearchInput.component.tsx": { "openmetadata-antd/no-antd-import": { "count": 1 @@ -1473,31 +1293,16 @@ "count": 3 } }, - "src/pages/AddQueryPage/AddQueryPage.component.tsx": { - "openmetadata-antd/no-antd-import": { - "count": 3 - } - }, "src/pages/AppearanceConfigSettingsPage/AppearanceConfigSettingsPage.tsx": { - "openmetadata-antd/no-antd-import": { - "count": 3 - } - }, - "src/pages/AuditLogsPage/AuditLogsPage.tsx": { "openmetadata-antd/no-antd-import": { "count": 1 } }, - "src/pages/Configuration/EditLoginConfiguration/EditLoginConfigurationPage.tsx": { + "src/pages/AuditLogsPage/AuditLogsPage.tsx": { "openmetadata-antd/no-antd-import": { "count": 1 } }, - "src/pages/Configuration/EditUrlConfiguration/EditUrlConfigurationPage.tsx": { - "openmetadata-antd/no-antd-import": { - "count": 2 - } - }, "src/pages/Configuration/LoginConfigurationDetails/LoginConfigurationPage.tsx": { "openmetadata-antd/no-antd-import": { "count": 2 @@ -1508,11 +1313,6 @@ "count": 1 } }, - "src/pages/CustomPropertiesPageV1/CustomPropertiesPageV1.tsx": { - "openmetadata-antd/no-antd-import": { - "count": 1 - } - }, "src/pages/CustomizeAppModeSidebarPage/CustomizeAppModeSidebarPage.tsx": { "openmetadata-antd/no-antd-import": { "count": 3 @@ -1538,57 +1338,12 @@ "count": 1 } }, - "src/pages/KPIPage/AddKPIPage.tsx": { - "openmetadata-antd/no-antd-import": { - "count": 8 - } - }, - "src/pages/KPIPage/EditKPIPage.tsx": { - "openmetadata-antd/no-antd-import": { - "count": 8 - } - }, "src/pages/LearningResourcesPage/LearningResourceForm.component.tsx": { - "openmetadata-antd/no-antd-import": { - "count": 5 - } - }, - "src/pages/LineageConfigPage/LineageConfigPage.tsx": { - "openmetadata-antd/no-antd-import": { - "count": 3 - } - }, - "src/pages/OnlineUsersPage/OnlineUsersPage.tsx": { - "openmetadata-antd/no-antd-import": { - "count": 1 - } - }, - "src/pages/Persona/PersonaDetailsPage/PersonaDetailsPage.tsx": { - "openmetadata-antd/no-antd-import": { - "count": 1 - } - }, - "src/pages/PlatformLineage/PlatformLineage.test.tsx": { - "openmetadata-antd/no-antd-import": { - "count": 1 - } - }, - "src/pages/PlatformLineage/PlatformLineage.tsx": { "openmetadata-antd/no-antd-import": { "count": 2 } }, - "src/pages/PoliciesPage/AddPolicyPage/AddPolicyPage.tsx": { - "openmetadata-antd/no-antd-import": { - "count": 2 - } - }, - "src/pages/PoliciesPage/PoliciesDetailPage/AddRulePage.tsx": { - "openmetadata-antd/no-antd-import": { - "count": 1 - } - }, - "src/pages/PoliciesPage/PoliciesDetailPage/EditRulePage.tsx": { + "src/pages/Persona/PersonaDetailsPage/PersonaDetailsPage.tsx": { "openmetadata-antd/no-antd-import": { "count": 1 } @@ -1598,24 +1353,9 @@ "count": 1 } }, - "src/pages/PoliciesPage/RuleForm/RuleForm.test.tsx": { - "openmetadata-antd/no-antd-import": { - "count": 1 - } - }, - "src/pages/PoliciesPage/RuleForm/RuleForm.tsx": { - "openmetadata-antd/no-antd-import": { - "count": 6 - } - }, "src/pages/RolesPage/AddAttributeModal/AddAttributeModal.tsx": { "openmetadata-antd/no-antd-import": { - "count": 3 - } - }, - "src/pages/RolesPage/AddRolePage/AddRolePage.tsx": { - "openmetadata-antd/no-antd-import": { - "count": 3 + "count": 1 } }, "src/pages/RolesPage/RolesDetailPage/RolesDetailPage.tsx": { @@ -1648,24 +1388,14 @@ "count": 1 } }, - "src/pages/TableDetailsPageV1/TableConstraints/TableConstraintsModal/TableConstraintsModal.component.tsx": { - "openmetadata-antd/no-antd-import": { - "count": 3 - } - }, "src/pages/TaskFormSettingsPage/TaskFormSettingsPage.tsx": { "openmetadata-antd/no-antd-import": { - "count": 5 + "count": 2 } }, "src/pages/TaskFormSettingsPage/components/TaskFormBuilderSection.tsx": { "openmetadata-antd/no-antd-import": { - "count": 6 - } - }, - "src/pages/TeamsPage/AddTeamForm.tsx": { - "openmetadata-antd/no-antd-import": { - "count": 5 + "count": 3 } }, "src/utils/Alerts/AlertsUtilPure.ts": { @@ -1673,11 +1403,6 @@ "count": 1 } }, - "src/utils/BotsUtils.tsx": { - "openmetadata-antd/no-antd-import": { - "count": 1 - } - }, "src/utils/CSV/CSVUtilsClassBase.tsx": { "openmetadata-antd/no-antd-import": { "count": 1 diff --git a/openmetadata-ui/src/main/resources/ui/playwright/constant/tableConstraint.ts b/openmetadata-ui/src/main/resources/ui/playwright/constant/tableConstraint.ts index 9db5553040ab..c54bc48d5c8c 100644 --- a/openmetadata-ui/src/main/resources/ui/playwright/constant/tableConstraint.ts +++ b/openmetadata-ui/src/main/resources/ui/playwright/constant/tableConstraint.ts @@ -10,51 +10,58 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -import { Page } from '@playwright/test'; +import { expect, Page } from '@playwright/test'; +import { selectOptionWithRetry } from '../utils/common'; -export const clickOnForeignKeySelector = async (page: Page) => { - await page - .getByTestId('constraint-type-select') - .getByText('Primary key') - .click(); - - const selectForeignConstraints = page.getByTitle('Foreign Key'); - await selectForeignConstraints.hover(); - await selectForeignConstraints.click(); +const selectConstraintType = async (page: Page, type: RegExp) => { + await selectOptionWithRetry( + page.getByTestId('constraint-type-select').getByRole('button'), + page.getByRole('option', { name: type }) + ); }; -export const clickOnUniqueKeySelector = async (page: Page) => { - await page - .getByTestId('constraint-type-select') - .getByTitle('Foreign key') - .click(); +export const clickOnForeignKeySelector = (page: Page) => + selectConstraintType(page, /^foreign key$/i); - const selectUniqueConstraints = page.getByTitle('Unique'); - await selectUniqueConstraints.hover(); - await selectUniqueConstraints.click(); -}; +export const clickOnUniqueKeySelector = (page: Page) => + selectConstraintType(page, /^unique$/i); + +export const clickOnDistKeySelector = (page: Page) => + selectConstraintType(page, /^dist key$/i); + +export const clickOnSortKeySelector = (page: Page) => + selectConstraintType(page, /^sort key$/i); -export const clickOnDistKeySelector = async ( +/** Adds each column as a chip to a key-constraint multi-select. */ +export const addKeyColumns = async ( page: Page, - isPrimary?: boolean + testId: string, + columns: string[] ) => { - await page - .getByTestId('constraint-type-select') - .getByTitle(isPrimary ? 'Primary key' : 'Unique') - .click(); - - const selectDistKeyConstraints = page.getByTitle('Dist key'); - await selectDistKeyConstraints.hover(); - await selectDistKeyConstraints.click(); -}; + const input = page.getByTestId(testId).getByRole('combobox'); + for (const column of columns) { + await input.fill(column); + await page.getByRole('option', { name: column, exact: true }).click(); + } + await page.keyboard.press('Escape'); -export const clickOnSortKeySelector = async (page: Page) => { - await page - .getByTestId('constraint-type-select') - .getByTitle('Dist key') - .click(); + for (const column of columns) { + await expect(page.getByTestId(testId)).toContainText(column); + } +}; - const selectSortKeyConstraints = page.getByTitle('Sort key'); - await selectSortKeyConstraints.hover(); - await selectSortKeyConstraints.click(); +/** Removes each column's chip from a key-constraint multi-select. */ +export const removeKeyColumns = async ( + page: Page, + testId: string, + columns: string[] +) => { + for (const column of columns) { + await page + .getByTestId(testId) + .getByTestId('autocomplete-selected-item') + .filter({ hasText: column }) + .getByRole('button') + .click(); + } }; diff --git a/openmetadata-ui/src/main/resources/ui/playwright/e2e/Features/CustomizeDetailPage.spec.ts b/openmetadata-ui/src/main/resources/ui/playwright/e2e/Features/CustomizeDetailPage.spec.ts index 142953572ef9..c972aac60e80 100644 --- a/openmetadata-ui/src/main/resources/ui/playwright/e2e/Features/CustomizeDetailPage.spec.ts +++ b/openmetadata-ui/src/main/resources/ui/playwright/e2e/Features/CustomizeDetailPage.spec.ts @@ -202,7 +202,11 @@ test.describe( await adminPage.getByText('Data Assets').click(); for (const type of Object.values(ECustomizedDataAssets)) { - await expect(adminPage.getByText(type, { exact: true })).toBeVisible(); + await expect( + adminPage + .getByTestId('customize-ui-options') + .getByText(type, { exact: true }) + ).toBeVisible(); } }); @@ -212,7 +216,11 @@ test.describe( await adminPage.getByText('Governance').click(); for (const type of Object.values(ECustomizedGovernance)) { - await expect(adminPage.getByText(type, { exact: true })).toBeVisible(); + await expect( + adminPage + .getByTestId('customize-ui-options') + .getByText(type, { exact: true }) + ).toBeVisible(); } }); @@ -420,7 +428,10 @@ test.describe('Persona customization', PLAYWRIGHT_BASIC_TEST_TAG_OBJ, () => { await adminPage.getByRole('tab', { name: 'Customize UI' }).click(); await adminPage.getByText('Data Assets').click(); - await adminPage.getByText(type, { exact: true }).click(); + await adminPage + .getByTestId('customize-ui-options') + .getByText(type, { exact: true }) + .click(); await waitForAllLoadersToDisappear(adminPage); @@ -576,7 +587,10 @@ test.describe('Persona customization', PLAYWRIGHT_BASIC_TEST_TAG_OBJ, () => { ); await adminPage.getByRole('tab', { name: 'Customize UI' }).click(); await adminPage.getByText('Governance').click(); - await adminPage.getByText(type, { exact: true }).click(); + await adminPage + .getByTestId('customize-ui-options') + .getByText(type, { exact: true }) + .click(); await waitForAllLoadersToDisappear(adminPage); @@ -793,7 +807,7 @@ test.describe('Persona customization', PLAYWRIGHT_BASIC_TEST_TAG_OBJ, () => { await navigateToPersonaWithPagination(adminPage, persona.data.name, true); await adminPage.getByRole('tab', { name: 'Customize UI' }).click(); await adminPage.getByText('Data Assets').click(); - await adminPage.getByText('Table', { exact: true }).click(); + await adminPage.getByTestId('Table').click(); await waitForAllLoadersToDisappear(adminPage); @@ -902,7 +916,7 @@ test.describe('Persona customization', PLAYWRIGHT_BASIC_TEST_TAG_OBJ, () => { await adminPage.getByRole('tab', { name: 'Customize UI' }).click(); await adminPage.getByText('Governance').click(); - await adminPage.getByText('Domain', { exact: true }).click(); + await adminPage.getByTestId('Domain').click(); await waitForAllLoadersToDisappear(adminPage); diff --git a/openmetadata-ui/src/main/resources/ui/playwright/e2e/Features/DataProductPersonaCustomization.spec.ts b/openmetadata-ui/src/main/resources/ui/playwright/e2e/Features/DataProductPersonaCustomization.spec.ts index 41ccb700535c..12007721a302 100644 --- a/openmetadata-ui/src/main/resources/ui/playwright/e2e/Features/DataProductPersonaCustomization.spec.ts +++ b/openmetadata-ui/src/main/resources/ui/playwright/e2e/Features/DataProductPersonaCustomization.spec.ts @@ -130,7 +130,7 @@ test.describe('Data Product Persona customization', () => { await adminPage.getByRole('tab', { name: 'Customize UI' }).click(); await adminPage.getByText('Governance').click(); - await adminPage.getByText('Data Product', { exact: true }).click(); + await adminPage.getByTestId('DataProduct').click(); await waitForAllLoadersToDisappear(adminPage); @@ -253,7 +253,7 @@ test.describe('Data Product Persona customization', () => { await adminPage.getByRole('tab', { name: 'Customize UI' }).click(); await adminPage.getByText('Governance').click(); - await adminPage.getByText('Data Product', { exact: true }).click(); + await adminPage.getByTestId('DataProduct').click(); await waitForAllLoadersToDisappear(adminPage); diff --git a/openmetadata-ui/src/main/resources/ui/playwright/e2e/Features/ExploreQuickFilters.spec.ts b/openmetadata-ui/src/main/resources/ui/playwright/e2e/Features/ExploreQuickFilters.spec.ts index 07a326d32289..34fcdc9ce409 100644 --- a/openmetadata-ui/src/main/resources/ui/playwright/e2e/Features/ExploreQuickFilters.spec.ts +++ b/openmetadata-ui/src/main/resources/ui/playwright/e2e/Features/ExploreQuickFilters.spec.ts @@ -739,22 +739,7 @@ test.describe('Metric search result highlight', () => { state: 'visible', }); await page.getByTestId('global-search-selector').click(); - await page.getByTestId('global-search-select-dropdown').waitFor({ - state: 'visible', - }); - - await page - .getByTestId('global-search-select-dropdown') - .locator('.rc-virtual-list-holder') - .evaluate((el) => { - el.scrollTop = el.scrollHeight; - }); - - const metricOption = page.getByTestId( - 'global-search-select-option-Metric' - ); - await metricOption.waitFor({ state: 'visible' }); - await metricOption.click(); + await page.getByTestId('global-search-select-option-Metric').click(); const searchResponse = page.waitForResponse( (response) => diff --git a/openmetadata-ui/src/main/resources/ui/playwright/e2e/Features/Glossary/GlossaryPersonaCustomization.spec.ts b/openmetadata-ui/src/main/resources/ui/playwright/e2e/Features/Glossary/GlossaryPersonaCustomization.spec.ts index 84682ab8c38a..15227360e26c 100644 --- a/openmetadata-ui/src/main/resources/ui/playwright/e2e/Features/Glossary/GlossaryPersonaCustomization.spec.ts +++ b/openmetadata-ui/src/main/resources/ui/playwright/e2e/Features/Glossary/GlossaryPersonaCustomization.spec.ts @@ -170,7 +170,7 @@ test.describe('Glossary persona customization', () => { await adminPage.getByRole('tab', { name: 'Customize UI' }).click(); await adminPage.getByText('Governance').click(); - await adminPage.getByText('Glossary', { exact: true }).click(); + await adminPage.getByTestId('Glossary').click(); await waitForAllLoadersToDisappear(adminPage); diff --git a/openmetadata-ui/src/main/resources/ui/playwright/e2e/Features/OnlineUsers.spec.ts b/openmetadata-ui/src/main/resources/ui/playwright/e2e/Features/OnlineUsers.spec.ts index 560b3e9dced8..f7b1fb7d26d4 100644 --- a/openmetadata-ui/src/main/resources/ui/playwright/e2e/Features/OnlineUsers.spec.ts +++ b/openmetadata-ui/src/main/resources/ui/playwright/e2e/Features/OnlineUsers.spec.ts @@ -95,7 +95,9 @@ test.describe('Online Users Feature', PLAYWRIGHT_BASIC_TEST_TAG_OBJ, () => { // Current selection should show "Last 24 hours" by default const timeFilterDropdown = page.getByTestId('time-window-select'); - await expect(timeFilterDropdown).toContainText('Last 24 hours'); + await expect(timeFilterDropdown.getByRole('button')).toHaveText( + 'Last 24 hours' + ); }); test('Should update user activity time when user navigates', async ({ @@ -151,22 +153,19 @@ test.describe('Online Users Feature', PLAYWRIGHT_BASIC_TEST_TAG_OBJ, () => { const timeFilterDropdown = page.getByTestId('time-window-select'); // Verify default filter is "Last 24 hours" - await expect(timeFilterDropdown).toContainText('Last 24 hours'); + await expect(timeFilterDropdown.getByRole('button')).toHaveText( + 'Last 24 hours' + ); // Click on the time filter dropdown await timeFilterDropdown.click(); - // Wait for dropdown to fully render - await expect(page.locator('.ant-select-dropdown:visible')).toBeVisible(); - await expect( - page.locator('.ant-select-dropdown:visible [title="Last 5 minutes"]') + page.getByRole('option', { name: 'Last 5 minutes' }) ).toBeVisible(); + await expect(page.getByRole('option', { name: 'Last hour' })).toBeVisible(); await expect( - page.locator('.ant-select-dropdown:visible [title="Last hour"]') - ).toBeVisible(); - await expect( - page.locator('.ant-select-dropdown:visible [title="Last 24 hours"]') + page.getByRole('option', { name: 'Last 24 hours' }) ).toBeVisible(); const onlineRes = page.waitForResponse( @@ -174,15 +173,15 @@ test.describe('Online Users Feature', PLAYWRIGHT_BASIC_TEST_TAG_OBJ, () => { ); // Select a different time window - await page - .locator('.ant-select-dropdown:visible [title="Last hour"]') - .click(); + await page.getByRole('option', { name: 'Last hour' }).click(); const onlineResponse = await onlineRes; expect(onlineResponse.status()).toBe(200); // Verify the filter has changed - await expect(timeFilterDropdown).toContainText('Last hour'); + await expect(timeFilterDropdown.getByRole('button')).toHaveText( + 'Last hour' + ); // The table should exist (with or without data) await expect(page.getByTestId('online-users-table')).toBeVisible(); diff --git a/openmetadata-ui/src/main/resources/ui/playwright/e2e/Features/QueryEntity.spec.ts b/openmetadata-ui/src/main/resources/ui/playwright/e2e/Features/QueryEntity.spec.ts index b56a074b4f27..3f2db964702b 100644 --- a/openmetadata-ui/src/main/resources/ui/playwright/e2e/Features/QueryEntity.spec.ts +++ b/openmetadata-ui/src/main/resources/ui/playwright/e2e/Features/QueryEntity.spec.ts @@ -85,25 +85,21 @@ test('Query Entity', async ({ page }) => { await page.click(descriptionBox); await page.keyboard.type(queryData.description); - await page + const queryUsedInInput = page .getByTestId('query-used-in') - .locator('div') - .filter({ hasText: 'Please Select a Query Used In' }) - .click(); + .getByRole('combobox'); + await queryUsedInInput.click(); const tableSearchResponse = page.waitForResponse( `/api/v1/search/query?q=*&index=table*` ); - await page.keyboard.type(queryData.queryUsedIn.table1); + await queryUsedInInput.fill(queryData.queryUsedIn.table1); await tableSearchResponse; await page - .locator('.ant-select-dropdown:visible .ant-select-item-option') - .filter({ - has: page.getByText(queryData.queryUsedIn.table1, { exact: true }), - }) + .getByRole('option', { name: queryData.queryUsedIn.table1, exact: true }) .click(); - await clickOutside(page); + await page.keyboard.press('Escape'); const createQueryResponse = page.waitForResponse('/api/v1/queries'); await page.click('[data-testid="save-btn"]'); @@ -205,14 +201,18 @@ test('Query Entity', async ({ page }) => { await page.click(CODE_EDITOR_LINE, { clickCount: 3 }); await page.keyboard.press('Backspace'); await page.keyboard.type(`${queryData.queryUsedIn.table1}`); - await page.click('[data-testid="edit-query-used-in"]'); const tableSearchResponse = page.waitForResponse( - '/api/v1/search/query?q=*&index=table*' + (response) => + response.url().includes('/api/v1/search/query') && + response.url().includes('index=table') ); - await page.keyboard.type(queryData.queryUsedIn.table2); + await page + .getByTestId('edit-query-used-in') + .getByRole('combobox') + .fill(queryData.queryUsedIn.table2); await tableSearchResponse; await page - .locator('.ant-select-dropdown:visible .ant-select-item-option') + .getByRole('option') .filter({ has: page.getByText(queryData.queryUsedIn.table2, { exact: true }), }) diff --git a/openmetadata-ui/src/main/resources/ui/playwright/e2e/Features/SSOConfiguration.spec.ts b/openmetadata-ui/src/main/resources/ui/playwright/e2e/Features/SSOConfiguration.spec.ts index 1dcd4a3fa05a..b5ae4021a00d 100644 --- a/openmetadata-ui/src/main/resources/ui/playwright/e2e/Features/SSOConfiguration.spec.ts +++ b/openmetadata-ui/src/main/resources/ui/playwright/e2e/Features/SSOConfiguration.spec.ts @@ -738,17 +738,17 @@ test.describe('SSO Configuration Tests', () => { // Open the roles dropdown — options are loaded from the API const roleInput = adminsMapping.rolesSelect.getByRole('combobox'); - await roleInput.focus(); - await roleInput.press('ArrowDown'); - const roleOption = page - .locator('.ant-select-dropdown:visible') - .getByTitle('Data Consumer', { exact: true }); + await roleInput.click(); + const roleOption = page.getByRole('option', { + name: 'Data Consumer', + exact: true, + }); await expect(roleOption).toBeVisible(); await roleOption.click(); await roleInput.press('Escape'); await expect( - adminsMapping.rolesSelect.getByTitle('Data Consumer', { exact: true }) - ).toBeVisible(); + adminsMapping.rolesSelect.getByTestId('autocomplete-selected-item') + ).toHaveText('Data Consumer'); // Add a second mapping with a duplicate DN — both rows show an error const duplicateMapping = await addMapping(); @@ -792,49 +792,50 @@ test.describe('SSO Configuration Tests', () => { const field = page.getByTestId( 'sso-configuration-form-array-field-template-authReassignRoles' ); - const dropdown = page.locator('.ant-select-dropdown:visible'); + const dropdown = page.getByRole('listbox'); + const selectedRoles = field.getByTestId('autocomplete-selected-item'); // Field renders as a combobox (not a plain tags input) await expect(field).toBeVisible(); await expect(field.getByRole('combobox')).toBeVisible(); // Opening the dropdown shows API-fetched role options - await field.click(); + await field.getByRole('combobox').click(); await expect(dropdown).toBeVisible(); - await expect(dropdown.locator('.ant-select-item-option')).not.toHaveCount( - 0 - ); + await expect(dropdown.getByRole('option')).not.toHaveCount(0); // Select a named role — it appears as a selection tag - await dropdown.getByTitle('Data Consumer', { exact: true }).click(); - await expect(field.locator('.ant-select-selection-item')).toHaveCount(1); + await dropdown + .getByRole('option', { name: 'Data Consumer', exact: true }) + .click(); + await expect(selectedRoles).toHaveCount(1); - // Remove the selected role via its remove button - await field.locator('.ant-select-selection-item-remove').click(); - await expect(field.locator('.ant-select-selection-item')).toHaveCount(0); + // Remove the selected role via its remove button; the open list hides + // the chips from the accessibility tree, so close it first. + await field.getByRole('combobox').press('Escape'); + await selectedRoles.getByRole('button').click(); + await expect(selectedRoles).toHaveCount(0); // Typing filters the visible options - await field.click(); + await field.getByRole('combobox').click(); const dataRoleSearchResponse = page.waitForResponse( '/api/v1/roles/search?*' ); - await field.locator('input').fill('Data'); + await field.getByRole('combobox').fill('Data'); await dataRoleSearchResponse; - await expect( - dropdown.locator( - '.ant-select-item-option:not(.ant-select-item-option-disabled)' - ) - ).not.toHaveCount(0, { timeout: 15000 }); + await expect(dropdown.getByRole('option')).not.toHaveCount(0, { + timeout: 15000, + }); // Pressing Enter on a non-existent value does not create an arbitrary tag - await field.locator('input').clear(); + await field.getByRole('combobox').clear(); const missingRoleSearchResponse = page.waitForResponse( '/api/v1/roles/search?*' ); - await field.locator('input').fill('NonExistentRoleXYZ123'); + await field.getByRole('combobox').fill('NonExistentRoleXYZ123'); await missingRoleSearchResponse; - await field.locator('input').press('Enter'); - await expect(field.locator('.ant-select-selection-item')).toHaveCount(0); + await field.getByRole('combobox').press('Enter'); + await expect(selectedRoles).toHaveCount(0); }); test('should not display role mapping widget for non-LDAP providers', async ({ diff --git a/openmetadata-ui/src/main/resources/ui/playwright/e2e/Features/SampleDataTableOperations.spec.ts b/openmetadata-ui/src/main/resources/ui/playwright/e2e/Features/SampleDataTableOperations.spec.ts index ef09cf171943..dc8ce7d386b9 100644 --- a/openmetadata-ui/src/main/resources/ui/playwright/e2e/Features/SampleDataTableOperations.spec.ts +++ b/openmetadata-ui/src/main/resources/ui/playwright/e2e/Features/SampleDataTableOperations.spec.ts @@ -142,7 +142,9 @@ test.describe('Sample Data Tab - Download and Delete Functionality', () => { await expect(page.getByTestId('row-limit-option-10')).toBeVisible(); await page.getByTestId('row-limit-option-10').click(); - await expect(page.getByTestId('row-limit-select')).toContainText('10'); + await expect( + page.getByTestId('row-limit-select').getByRole('button') + ).toHaveText('10'); }); await test.step('Change row limit to 1000', async () => { @@ -151,7 +153,9 @@ test.describe('Sample Data Tab - Download and Delete Functionality', () => { await expect(page.getByTestId('row-limit-option-1000')).toBeVisible(); await page.getByTestId('row-limit-option-1000').click(); - await expect(page.getByTestId('row-limit-select')).toContainText('1000'); + await expect( + page.getByTestId('row-limit-select').getByRole('button') + ).toHaveText('1000'); }); await test.step('Reset row limit back to 100', async () => { @@ -160,7 +164,9 @@ test.describe('Sample Data Tab - Download and Delete Functionality', () => { await expect(page.getByTestId('row-limit-option-100')).toBeVisible(); await page.getByTestId('row-limit-option-100').click(); - await expect(page.getByTestId('row-limit-select')).toContainText('100'); + await expect( + page.getByTestId('row-limit-select').getByRole('button') + ).toHaveText('100'); }); }); @@ -400,7 +406,9 @@ test.describe('Sample Data Tab - Download and Delete Functionality', () => { await expect(page.getByTestId('row-limit-option-10')).toBeVisible(); await page.getByTestId('row-limit-option-10').click(); - await expect(page.getByTestId('row-limit-select')).toContainText('10'); + await expect( + page.getByTestId('row-limit-select').getByRole('button') + ).toHaveText('10'); }); await test.step('Switch to Schema tab and back to Sample Data', async () => { diff --git a/openmetadata-ui/src/main/resources/ui/playwright/e2e/Features/TableConstraint.spec.ts b/openmetadata-ui/src/main/resources/ui/playwright/e2e/Features/TableConstraint.spec.ts index 968fc0a59905..072ae1c89261 100644 --- a/openmetadata-ui/src/main/resources/ui/playwright/e2e/Features/TableConstraint.spec.ts +++ b/openmetadata-ui/src/main/resources/ui/playwright/e2e/Features/TableConstraint.spec.ts @@ -13,16 +13,18 @@ import test, { expect } from '@playwright/test'; import { SearchIndex } from '../../../src/enums/search.enum'; import { + addKeyColumns, clickOnDistKeySelector, clickOnForeignKeySelector, clickOnSortKeySelector, clickOnUniqueKeySelector, + removeKeyColumns, } from '../../constant/tableConstraint'; import { TableClass } from '../../support/entity/TableClass'; import { - clickOutside, createNewPage, redirectToHomePage, + selectOptionWithRetry, uuid, } from '../../utils/common'; import { waitForAllLoadersToDisappear } from '../../utils/entity'; @@ -72,44 +74,10 @@ test.describe('Table Constraints', {}, () => { await expect(page.getByTestId('constraint-type-select')).toContainText( 'Primary key' ); - await expect( - page.getByTestId('primary-constraint-type-select') - ).toBeVisible(); - - await page - .locator( - '[data-testid="primary-constraint-type-select"] > .ant-select-selector .ant-select-selection-search-input' - ) - .click(); - - await page - .getByTestId('primary-constraint-type-select') - .getByRole('combobox') - // eslint-disable-next-line playwright/no-force-option -- Ant Select selected item overlay covers combobox input - .fill(columnName1, { force: true }); - - // select 1st value from dropdown - const firstPrimaryKeyColumn = page.getByTitle(columnName1); - await firstPrimaryKeyColumn.hover(); - await firstPrimaryKeyColumn.click(); - - // select 2nd value from dropdown - await page - .getByTestId('primary-constraint-type-select') - .getByRole('combobox') - // eslint-disable-next-line playwright/no-force-option -- Ant Select selected item overlay covers combobox input - .fill(columnName2, { force: true }); - - const secondPrimaryKeyColumn = page.getByTitle(columnName2); - await secondPrimaryKeyColumn.hover(); - await secondPrimaryKeyColumn.click(); - await clickOutside(page); - - await expect( - page - .getByTestId('primary-constraint-type-select') - .getByText(`${columnName1}${columnName2}`) - ).toBeVisible(); + await addKeyColumns(page, 'primary-constraint-type-select', [ + columnName1, + columnName2, + ]); // Foreign Key Constraint Section @@ -120,38 +88,16 @@ test.describe('Table Constraints', {}, () => { await clickOnForeignKeySelector(page); - await page - .locator( - '[data-testid="0-column-type-select"] > .ant-select-selector .ant-select-selection-search-input' - ) - .click(); - - // select value from dropdown - const columnNameDropdownValue = page - .getByTitle(columnName) - .locator('div'); - await columnNameDropdownValue.hover(); - await columnNameDropdownValue.click(); - await clickOutside(page); - - await page - .locator( - '[data-testid="0-relationship-type-select"] > .ant-select-selector .ant-select-selection-search-input' - ) - .click(); - - // select value from dropdown - const relationTypeDropdownValue = page.getByText('One to One'); - await relationTypeDropdownValue.hover(); - await relationTypeDropdownValue.click(); - await clickOutside(page); - - // select 2nd Table column as related column - const relatedColumnSelect = page.locator( - '[data-testid="0-related-column-select"] > .ant-select-selector .ant-select-selection-search-input' + await selectOptionWithRetry( + page.getByTestId('0-column-type-select').getByRole('button'), + page.getByRole('option', { name: columnName, exact: true }) + ); + await selectOptionWithRetry( + page.getByTestId('0-relationship-type-select').getByRole('button'), + page.getByRole('option', { name: 'One to One', exact: true }) ); - await relatedColumnSelect.click(); + // select 2nd Table column as related column const querySearchResponse = page.waitForResponse((response) => { const url = new URL(response.url()); const query = (url.searchParams.get('q') ?? '').replace(/\\(.)/g, '$1'); @@ -163,132 +109,35 @@ test.describe('Table Constraints', {}, () => { query.includes(relatedColumnFQN) ); }); - await relatedColumnSelect.fill(relatedColumnFQN); + await page + .getByTestId('0-related-column-select') + .getByRole('combobox') + .fill(relatedColumnFQN); expect((await querySearchResponse).status()).toBe(200); - // select value from dropdown - const dropdownValue = page.getByTestId( - `option-label-${relatedColumnFQN}` - ); - await dropdownValue.click(); - await clickOutside(page); + await page.getByTestId(`option-label-${relatedColumnFQN}`).click(); // Unique Constraint Section await clickOnUniqueKeySelector(page); - - await page - .locator( - '[data-testid="unique-constraint-type-select"] > .ant-select-selector .ant-select-selection-search-input' - ) - .click(); - - await page - .getByTestId('unique-constraint-type-select') - .getByRole('combobox') - // eslint-disable-next-line playwright/no-force-option -- Ant Select selected item overlay covers combobox input - .fill(columnName3, { force: true }); - - // select 1st value from dropdown - const firstUniqueKeyColumn = page.getByTitle(columnName3, { - exact: true, - }); - await firstUniqueKeyColumn.hover(); - await firstUniqueKeyColumn.click(); - - // select 2nd value from dropdown - await page - .getByTestId('unique-constraint-type-select') - .getByRole('combobox') - // eslint-disable-next-line playwright/no-force-option -- Ant Select selected item overlay covers combobox input - .fill(columnName4, { force: true }); - - const secondUniqueKeyColumn = page.getByTitle(columnName4); - await secondUniqueKeyColumn.hover(); - await secondUniqueKeyColumn.click(); - await clickOutside(page); - - await expect( - page - .getByTestId('unique-constraint-type-select') - .getByText(`${columnName3}${columnName4}`) - ).toBeVisible(); + await addKeyColumns(page, 'unique-constraint-type-select', [ + columnName3, + columnName4, + ]); // Dist Constraint Section await clickOnDistKeySelector(page); - - await page - .locator( - '[data-testid="dist-constraint-type-select"] > .ant-select-selector .ant-select-selection-search-input' - ) - .click(); - - await page - .getByTestId('dist-constraint-type-select') - .getByRole('combobox') - // eslint-disable-next-line playwright/no-force-option -- Ant Select selected item overlay covers combobox input - .fill(columnName1, { force: true }); - - // select 1st value from dropdown - const firstDistKeyColumn = page.getByTitle(columnName1); - await firstDistKeyColumn.hover(); - await firstDistKeyColumn.click(); - - // select 2nd value from dropdown - await page - .getByTestId('dist-constraint-type-select') - .getByRole('combobox') - // eslint-disable-next-line playwright/no-force-option -- Ant Select selected item overlay covers combobox input - .fill(columnName2, { force: true }); - - const secondDistKeyColumn = page.getByTitle(columnName2); - await secondDistKeyColumn.hover(); - await secondDistKeyColumn.click(); - await clickOutside(page); - - await expect( - page - .getByTestId('dist-constraint-type-select') - .getByText(`${columnName1}${columnName2}`) - ).toBeVisible(); + await addKeyColumns(page, 'dist-constraint-type-select', [ + columnName1, + columnName2, + ]); // Sort Constraint Section await clickOnSortKeySelector(page); - - await page - .locator( - '[data-testid="sort-constraint-type-select"] > .ant-select-selector .ant-select-selection-search-input' - ) - .click(); - - await page - .getByTestId('sort-constraint-type-select') - .getByRole('combobox') - // eslint-disable-next-line playwright/no-force-option -- Ant Select selected item overlay covers combobox input - .fill(columnName3, { force: true }); - - // select 1st value from dropdown - const firstSortKeyColumn = page.getByTitle(columnName3, { exact: true }); - await firstSortKeyColumn.hover(); - await firstSortKeyColumn.click(); - - // select 2nd value from dropdown - await page - .getByTestId('sort-constraint-type-select') - .getByRole('combobox') - // eslint-disable-next-line playwright/no-force-option -- Ant Select selected item overlay covers combobox input - .fill(columnName4, { force: true }); - - const secondSortKeyColumn = page.getByTitle(columnName4); - await secondSortKeyColumn.hover(); - await secondSortKeyColumn.click(); - await clickOutside(page); - - await expect( - page - .getByTestId('sort-constraint-type-select') - .getByText(`${columnName3}${columnName4}`) - ).toBeVisible(); + await addKeyColumns(page, 'sort-constraint-type-select', [ + columnName3, + columnName4, + ]); const saveResponse = page.waitForResponse('/api/v1/tables/*'); await page.click('[data-testid="save-btn"]'); @@ -342,9 +191,10 @@ test.describe('Table Constraints', {}, () => { await page.getByTestId('edit-table-constraint-button').click(); // Clear Primary Key - await page.click( - '[data-testid="primary-constraint-type-select"] .anticon-close-circle' - ); + await removeKeyColumns(page, 'primary-constraint-type-select', [ + columnName1, + columnName2, + ]); // Clear Foreign Key await clickOnForeignKeySelector(page); @@ -352,9 +202,10 @@ test.describe('Table Constraints', {}, () => { // Clear Unique Key await clickOnUniqueKeySelector(page); - await page.click( - '[data-testid="unique-constraint-type-select"] .anticon-close-circle' - ); + await removeKeyColumns(page, 'unique-constraint-type-select', [ + columnName3, + columnName4, + ]); const saveResponseOne = page.waitForResponse('/api/v1/tables/*'); await page.click('[data-testid="save-btn"]'); @@ -379,16 +230,18 @@ test.describe('Table Constraints', {}, () => { await page.getByTestId('table-constraint-modal').waitFor(); // Clear Dist Key - await clickOnDistKeySelector(page, true); - await page.click( - '[data-testid="dist-constraint-type-select"] .anticon-close-circle' - ); + await clickOnDistKeySelector(page); + await removeKeyColumns(page, 'dist-constraint-type-select', [ + columnName1, + columnName2, + ]); // Clear Sort Key await clickOnSortKeySelector(page); - await page.click( - '[data-testid="sort-constraint-type-select"] .anticon-close-circle' - ); + await removeKeyColumns(page, 'sort-constraint-type-select', [ + columnName3, + columnName4, + ]); const saveResponse = page.waitForResponse('/api/v1/tables/*'); await page.click('[data-testid="save-btn"]'); diff --git a/openmetadata-ui/src/main/resources/ui/playwright/e2e/Flow/AddRoleAndAssignToUser.spec.ts b/openmetadata-ui/src/main/resources/ui/playwright/e2e/Flow/AddRoleAndAssignToUser.spec.ts index 06aabe2148d6..4e11ef5b910e 100644 --- a/openmetadata-ui/src/main/resources/ui/playwright/e2e/Flow/AddRoleAndAssignToUser.spec.ts +++ b/openmetadata-ui/src/main/resources/ui/playwright/e2e/Flow/AddRoleAndAssignToUser.spec.ts @@ -53,9 +53,12 @@ test.describe.serial('Add role and assign it to the user', () => { await page.fill('[data-testid="name"]', roleName); await fillDescriptionBox(page, `description for ${roleName}`); - await page.click('[data-testid="policies"]'); - await page.click('[title="Data Consumer Policy"]'); - await page.click('[title="Data Steward Policy"]'); + const policiesInput = page.getByTestId('policies').getByRole('combobox'); + for (const policy of ['Data Consumer Policy', 'Data Steward Policy']) { + await policiesInput.fill('Data'); + await page.getByRole('option', { name: policy, exact: true }).click(); + } + await policiesInput.press('Escape'); const policyResponse = page.waitForResponse(`/api/v1/roles`); @@ -94,16 +97,16 @@ test.describe.serial('Add role and assign it to the user', () => { await page.click('[data-testid="password-generator"]'); await generatePasswordResponse; - await expect(page.locator('#generatedPassword')).toHaveValue(/\S+/); + await expect(page.getByTestId('generated-password')).toHaveValue(/\S+/); - await page.click('[data-testid="roles-dropdown"]'); - await page.locator('.ant-select-dropdown').waitFor({ - state: 'visible', - }); + const rolesCombobox = page + .getByTestId('roles-dropdown') + .getByRole('combobox'); + await rolesCombobox.click(); const rolesSearchResponse = page.waitForResponse('/api/v1/roles/search?*'); - await page.fill('#roles', roleName); + await rolesCombobox.fill(roleName); await rolesSearchResponse; - await page.click(`[title="${roleName}"]`); + await page.getByRole('option', { name: roleName, exact: true }).click(); await page.keyboard.press('Escape'); diff --git a/openmetadata-ui/src/main/resources/ui/playwright/e2e/Flow/LineageSettings.spec.ts b/openmetadata-ui/src/main/resources/ui/playwright/e2e/Flow/LineageSettings.spec.ts index b3f6b8e8c00f..8f31bbb6d990 100644 --- a/openmetadata-ui/src/main/resources/ui/playwright/e2e/Flow/LineageSettings.spec.ts +++ b/openmetadata-ui/src/main/resources/ui/playwright/e2e/Flow/LineageSettings.spec.ts @@ -24,7 +24,11 @@ import { SearchIndexClass } from '../../support/entity/SearchIndexClass'; import { TableClass } from '../../support/entity/TableClass'; import { TopicClass } from '../../support/entity/TopicClass'; import { performAdminLogin } from '../../utils/admin'; -import { redirectToHomePage, toastNotification } from '../../utils/common'; +import { + redirectToHomePage, + selectOptionWithRetry, + toastNotification, +} from '../../utils/common'; import { waitForAllLoadersToDisappear } from '../../utils/entity'; import { activateColumnLayer, @@ -271,9 +275,10 @@ test.describe.serial( await page.getByTestId('preferences').click(); await page.getByTestId('preferences.lineageConfig').click(); - await page.getByTestId('field-pipeline-view-mode').click(); - - await page.getByTitle('Edge').filter({ visible: true }).click(); + await selectOptionWithRetry( + page.getByTestId('field-pipeline-view-mode').getByRole('button'), + page.getByRole('option', { name: 'Edge', exact: true }) + ); const lineageSettingUpdate = page.waitForResponse( '/api/v1/system/settings' ); @@ -309,8 +314,10 @@ test.describe.serial( ).toBeVisible({ timeout: 10000 }); // update pipeline view mode to Node - await page.getByTestId('field-pipeline-view-mode').click(); - await page.getByTitle('Node').filter({ visible: true }).click(); + await selectOptionWithRetry( + page.getByTestId('field-pipeline-view-mode').getByRole('button'), + page.getByRole('option', { name: 'Node', exact: true }) + ); const settingsUpdate = page.waitForResponse('/api/v1/system/settings'); await page.getByTestId('save-button').click(); await settingsUpdate; diff --git a/openmetadata-ui/src/main/resources/ui/playwright/e2e/Flow/MetricSearch.spec.ts b/openmetadata-ui/src/main/resources/ui/playwright/e2e/Flow/MetricSearch.spec.ts index 0abb682b9ca2..ba3ef082a3c2 100644 --- a/openmetadata-ui/src/main/resources/ui/playwright/e2e/Flow/MetricSearch.spec.ts +++ b/openmetadata-ui/src/main/resources/ui/playwright/e2e/Flow/MetricSearch.spec.ts @@ -77,24 +77,7 @@ test.describe( await test.step('Select Metric search index and search', async () => { await page.getByTestId('global-search-selector').click(); - // Wait for dropdown to be visible - await page.getByTestId('global-search-select-dropdown').waitFor({ - state: 'visible', - }); - - // Scroll within the dropdown to find Metric option - const dropdownMenu = page.locator( - '.global-search-select-menu .rc-virtual-list-holder' - ); - await dropdownMenu.evaluate((el) => { - el.scrollTop = el.scrollHeight; - }); - - const metricOption = page.getByTestId( - 'global-search-select-option-Metric' - ); - await metricOption.waitFor({ state: 'visible' }); - await metricOption.click(); + await page.getByTestId('global-search-select-option-Metric').click(); const searchQuery = `AcceleratedConnection WBA Ethernet ${metricSuffix}`; diff --git a/openmetadata-ui/src/main/resources/ui/playwright/e2e/Flow/Navbar.spec.ts b/openmetadata-ui/src/main/resources/ui/playwright/e2e/Flow/Navbar.spec.ts index c5480d01aa02..8ff522d022d6 100644 --- a/openmetadata-ui/src/main/resources/ui/playwright/e2e/Flow/Navbar.spec.ts +++ b/openmetadata-ui/src/main/resources/ui/playwright/e2e/Flow/Navbar.spec.ts @@ -50,9 +50,9 @@ for (const searchItem of navbarSearchItems) { label ); - await expect(page.getByTestId('global-search-selector')).toContainText( - label - ); + await expect( + page.getByTestId('global-search-selector').getByRole('button') + ).toHaveText(label); const searchRes = waitForResponseWithStatus( page, 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 a126f4276971..bde29343078c 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 @@ -521,25 +521,25 @@ test.describe.serial('Team persona setting flow', () => { // Wait for dropdown to open and options to load await adminPage.getByTestId('default-persona-select-list').waitFor(); - await adminPage.locator('.ant-select-dropdown').waitFor({ - state: 'visible', - }); + await adminPage.getByRole('listbox').waitFor({ state: 'visible' }); const personaResponse = await personasLoadResponse; expect(personaResponse.status()).toBe(200); - const option = adminPage.locator( - `.ant-select-dropdown:visible [title="${teamPersona.responseData.displayName}"]` - ); + const option = adminPage.getByRole('option', { + name: teamPersona.responseData.displayName, + }); await expect(option).toBeVisible(); await option.click(); + const selectedPersona = adminPage + .getByTestId('default-persona-select-list') + .getByTestId('autocomplete-selected-item'); + // Verify the selected option is correct - await expect( - adminPage.locator( - `span.ant-select-selection-item[title="${teamPersona.responseData.displayName}"]` - ) - ).toBeVisible(); + await expect(selectedPersona).toContainText( + teamPersona.responseData.displayName + ); const teamPatchResponse = adminPage.waitForResponse('/api/v1/teams/*'); @@ -550,9 +550,7 @@ test.describe.serial('Team persona setting flow', () => { await teamPatchResponse; // Ensure dropdown closed - await expect( - adminPage.locator('.ant-select-dropdown:visible') - ).not.toBeVisible(); + await expect(adminPage.getByRole('listbox')).not.toBeVisible(); // Verify persona renders in team UI await expect(adminPage.getByTestId('team-persona')).toContainText( @@ -565,12 +563,12 @@ test.describe.serial('Team persona setting flow', () => { ); await adminPage.getByTestId('default-edit-user-persona').click(); await adminPage.getByTestId('default-persona-select-list').waitFor(); - await adminPage.locator('.ant-select-dropdown').waitFor({ - state: 'visible', - }); const personaResponse2 = await personasLoadResponse2; expect(personaResponse2.status()).toBe(200); + // Single select: the current default is removed before picking another. + await selectedPersona.getByRole('button').click(); + // Typing filters via the server-side search endpoint, not client-side. const personaSearchResponse = adminPage.waitForResponse( (response) => @@ -578,29 +576,24 @@ test.describe.serial('Team persona setting flow', () => { response.request().method() === 'GET' ); await adminPage - .locator('[data-testid="default-persona-select-list"] input') + .getByTestId('default-persona-select-list') + .getByRole('combobox') .fill(teamPersona2.responseData.displayName); const searchResponse = await personaSearchResponse; expect(searchResponse.status()).toBe(200); // Click the new persona (teamPersona2) - const userPersonaOption = adminPage.locator( - `.ant-select-dropdown:visible [title="${teamPersona2.responseData.displayName}"]` - ); + const userPersonaOption = adminPage.getByRole('option', { + name: teamPersona2.responseData.displayName, + }); await expect(userPersonaOption).toBeVisible(); await userPersonaOption.click(); - // Verify the new option replaces the old option in the dropdown selection display - await expect( - adminPage.locator( - `span.ant-select-selection-item[title="${teamPersona2.responseData.displayName}"]` - ) - ).toBeVisible(); - await expect( - adminPage.locator( - `span.ant-select-selection-item[title="${teamPersona.responseData.displayName}"]` - ) - ).not.toBeVisible(); + // Verify the new option replaces the old option in the selection display + await expect(selectedPersona).toHaveCount(1); + await expect(selectedPersona).toContainText( + teamPersona2.responseData.displayName + ); // Save it and re-verify const teamPatchSwitchResponse = @@ -619,13 +612,15 @@ test.describe.serial('Team persona setting flow', () => { response.url().includes('/api/v1/personas') ); await adminPage.getByTestId('default-edit-user-persona').click(); - await adminPage.locator('.ant-select-dropdown').waitFor({ - state: 'visible', - }); await personasLoadResponse3; - const revertOption = adminPage.locator( - `.ant-select-dropdown:visible [title="${teamPersona.responseData.displayName}"]` - ); + await selectedPersona.getByRole('button').click(); + await adminPage + .getByTestId('default-persona-select-list') + .getByRole('combobox') + .click(); + const revertOption = adminPage.getByRole('option', { + name: teamPersona.responseData.displayName, + }); await expect(revertOption).toBeVisible(); await revertOption.click(); const teamPatchRevertResponse = @@ -713,16 +708,17 @@ test.describe.serial('Team persona setting flow', () => { await adminPage.getByTestId('default-persona-select-list').waitFor(); - // Hover over the select to reveal the clear button, then click it await adminPage - .locator('[data-testid="default-persona-select-list"]') - .hover(); + .getByTestId('default-persona-select-list') + .getByTestId('autocomplete-selected-item') + .getByRole('button') + .click(); + // Removing the chip refocuses the field, which reopens the list over Save. await adminPage - .locator( - '[data-testid="default-persona-select-list"] .ant-select-clear' - ) - .click(); + .getByTestId('default-persona-select-list') + .getByRole('combobox') + .press('Escape'); const defaultPersonaChangeResponse = adminPage.waitForResponse('/api/v1/teams/*'); 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 3c89c747fcf2..38d7c7ba73d6 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 @@ -111,6 +111,7 @@ import { navigateToExploreAndSelectEntity } from '../../utils/explore'; import { createTable } from '../../utils/KnowledgeCenter'; import { openMatchingFieldsPanel, + selectSettingOption, setSliderValue, } from '../../utils/searchSettingUtils'; import { @@ -3452,13 +3453,7 @@ pickEntityMatrix( ); const matchTypeSelect = page.getByTestId('match-type-select'); - await matchTypeSelect.click(); - await page - .locator('.ant-select-dropdown:not(.ant-select-dropdown-hidden)') - .waitFor({ state: 'visible' }); - await page - .locator('.ant-select-item-option[title="Standard Match"]') - .click(); + await selectSettingOption(page, matchTypeSelect, 'Standard Match'); const searchSettingsSaveResponse = page.waitForResponse( '/api/v1/system/settings' @@ -3597,10 +3592,7 @@ pickEntityMatrix( ); const matchTypeSelect = page.getByTestId('match-type-select'); - await matchTypeSelect.click(); - await page - .locator('.ant-select-item-option[title="Phrase Match"]') - .click(); + await selectSettingOption(page, matchTypeSelect, 'Phrase Match'); const searchSettingsSaveResponse = page.waitForResponse( '/api/v1/system/settings' 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 be98036019b7..2a3c3cb88914 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 @@ -2535,6 +2535,7 @@ dataContractEntities.forEach((EntityClass) => { .getByText('Data Assets') .click(); await page + .getByTestId('customize-ui-options') .getByText(entityName[entityType] ?? entityType, { exact: true, }) diff --git a/openmetadata-ui/src/main/resources/ui/playwright/e2e/Pages/DescriptionVisibility.spec.ts b/openmetadata-ui/src/main/resources/ui/playwright/e2e/Pages/DescriptionVisibility.spec.ts index be4a3e844e31..e43131afb0e8 100644 --- a/openmetadata-ui/src/main/resources/ui/playwright/e2e/Pages/DescriptionVisibility.spec.ts +++ b/openmetadata-ui/src/main/resources/ui/playwright/e2e/Pages/DescriptionVisibility.spec.ts @@ -355,7 +355,7 @@ test.describe( await adminPage.getByRole('tab', { name: 'Customize UI' }).click(); await adminPage.getByText('Data Assets').click(); - await adminPage.getByText('Table', { exact: true }).click(); + await adminPage.getByTestId('Table').click(); await waitForAllLoadersToDisappear(adminPage); // Add custom tab diff --git a/openmetadata-ui/src/main/resources/ui/playwright/e2e/Pages/LearningResources.spec.ts b/openmetadata-ui/src/main/resources/ui/playwright/e2e/Pages/LearningResources.spec.ts index 5b427acdc702..e23d92d66b9b 100644 --- a/openmetadata-ui/src/main/resources/ui/playwright/e2e/Pages/LearningResources.spec.ts +++ b/openmetadata-ui/src/main/resources/ui/playwright/e2e/Pages/LearningResources.spec.ts @@ -21,6 +21,7 @@ import { createNewPage, getApiContext, redirectToHomePage, + selectOptionWithRetry, uuid, } from '../../utils/common'; import { waitForAllLoadersToDisappear } from '../../utils/entity'; @@ -48,14 +49,28 @@ async function goToLearningResourcesAdmin(page: Page) { await expect(page.getByTestId('learning-resources-table-body')).toBeVisible(); } -async function selectDropdownOption(page: Page, optionText: string) { - const option = page - .locator('.ant-select-dropdown:visible') - .locator('.ant-select-item-option') - .filter({ hasText: new RegExp(`^${optionText}$`, 'i') }); - - await expect(option).toBeVisible(); - await option.click(); +async function selectDropdownOption( + page: Page, + testId: string, + optionText: string, + role: 'button' | 'combobox' = 'button' +) { + const trigger = page + .getByTestId('learning-resource-form-drawer') + .getByTestId(testId) + .getByRole(role); + await selectOptionWithRetry( + trigger, + page.getByRole('option', { name: new RegExp(`^${optionText}$`, 'i') }) + ); + // A multi-select stays open; Escape would bubble to the drawer and close it. + if ((await trigger.getAttribute('aria-expanded')) === 'true') { + await page + .getByTestId('learning-resource-form-drawer') + .getByText(/^(Add|Edit) Resource$/) + .click(); + } + await expect(trigger).toHaveAttribute('aria-expanded', 'false'); } async function fillResourceForm( @@ -70,52 +85,30 @@ async function fillResourceForm( status: string; } ) { - const nameInput = page.getByTestId('name-input'); + const nameInput = page.getByTestId('name-input').getByRole('textbox'); await expect(nameInput).toBeVisible(); await nameInput.fill(data.resourceName); - const descriptionInput = page.getByTestId('description-input'); + const descriptionInput = page + .getByTestId('description-input') + .getByRole('textbox'); await expect(descriptionInput).toBeVisible(); await descriptionInput.fill(data.description); - const typeSelector = page - .getByTestId('resource-type-form-item') - .locator('.ant-select-selector'); - await expect(typeSelector).toBeVisible(); - await typeSelector.click(); - await selectDropdownOption(page, data.type); - - const categorySelector = page - .getByTestId('categories-form-item') - .locator('.ant-select-selector'); - await expect(categorySelector).toBeVisible(); - await categorySelector.click(); - await selectDropdownOption(page, data.category); - await page.locator('.ant-drawer-header').click(); - await expect(page.locator('.ant-select-dropdown:visible')).not.toBeVisible(); - - const contextSelector = page - .getByTestId('contexts-form-item') - .locator('.ant-select-selector'); - await expect(contextSelector).toBeVisible(); - await contextSelector.click(); - await page - .locator('.ant-select-dropdown:visible') - .getByTitle(data.context, { exact: true }) - .click(); - await page.locator('.ant-drawer-header').click(); - await expect(page.locator('.ant-select-dropdown:visible')).not.toBeVisible(); - - const urlInput = page.getByTestId('source-url-input'); + await selectDropdownOption(page, 'resource-type-select', data.type); + await selectDropdownOption( + page, + 'categories-select', + data.category, + 'combobox' + ); + await selectDropdownOption(page, 'contexts-select', data.context, 'combobox'); + + const urlInput = page.getByTestId('source-url-input').getByRole('textbox'); await expect(urlInput).toBeVisible(); await urlInput.fill(data.url); - const statusSelector = page - .getByTestId('status-form-item') - .locator('.ant-select-selector'); - await expect(statusSelector).toBeVisible(); - await statusSelector.click(); - await selectDropdownOption(page, data.status); + await selectDropdownOption(page, 'status-select', data.status); } async function scrollDrawerToShowResource(page: Page, resourceText: string) { diff --git a/openmetadata-ui/src/main/resources/ui/playwright/e2e/Pages/Lineage/LineageControls.spec.ts b/openmetadata-ui/src/main/resources/ui/playwright/e2e/Pages/Lineage/LineageControls.spec.ts index e2d23737269d..44eae14324eb 100644 --- a/openmetadata-ui/src/main/resources/ui/playwright/e2e/Pages/Lineage/LineageControls.spec.ts +++ b/openmetadata-ui/src/main/resources/ui/playwright/e2e/Pages/Lineage/LineageControls.spec.ts @@ -211,10 +211,8 @@ test.describe('Lineage Layers', PLAYWRIGHT_BASIC_TEST_TAG_OBJ, () => { await searchSelect.click(); - await page - .locator( - '[data-testid="search-entity-select"] .ant-select-selection-search-input' - ) + await searchSelect + .getByRole('combobox') .fill('invalid_fqn_does_not_exist_12345'); const noResultsText = page.getByText(/no match/i); diff --git a/openmetadata-ui/src/main/resources/ui/playwright/e2e/Pages/Lineage/PlatformLineage.spec.ts b/openmetadata-ui/src/main/resources/ui/playwright/e2e/Pages/Lineage/PlatformLineage.spec.ts index ac9bfbcacc38..546568452f47 100644 --- a/openmetadata-ui/src/main/resources/ui/playwright/e2e/Pages/Lineage/PlatformLineage.spec.ts +++ b/openmetadata-ui/src/main/resources/ui/playwright/e2e/Pages/Lineage/PlatformLineage.spec.ts @@ -21,7 +21,6 @@ import { getDefaultAdminAPIContext, redirectToHomePage, uuid, - waitForAntdPopupToSettle, } from '../../../utils/common'; import { waitForAllLoadersToDisappear } from '../../../utils/entity'; import { @@ -99,14 +98,13 @@ test.describe('Entity Lineage tab', () => { req.url().includes('/api/v1/search/query') && req.url().includes('deleted=false') ); - await page.fill( - '[data-testid="search-entity-select"] .ant-select-selection-search-input', - table.entity.name - ); + await page + .getByTestId('search-entity-select') + .getByRole('combobox') + .fill(table.entity.name); await searchRequest; - await page.locator('.ant-select-dropdown').waitFor(); - await waitForAntdPopupToSettle(page); + await page.getByRole('listbox').waitFor(); const nodeFqn = get(table, 'entityResponseData.fullyQualifiedName', ''); const dbFqn = get( @@ -142,12 +140,11 @@ test.describe('Entity Lineage tab', () => { await page.getByTestId('search-entity-select').waitFor(); await page.click('[data-testid="search-entity-select"]'); - await page.fill( - '[data-testid="search-entity-select"] .ant-select-selection-search-input', - db - ); + await page + .getByTestId('search-entity-select') + .getByRole('combobox') + .fill(db); await page.getByTestId(`node-suggestion-${dbFqn}`).waitFor(); - await waitForAntdPopupToSettle(page); const databaseSceneResponse = waitForResponseWithStatus( page, (response) => diff --git a/openmetadata-ui/src/main/resources/ui/playwright/e2e/Pages/Policies.spec.ts b/openmetadata-ui/src/main/resources/ui/playwright/e2e/Pages/Policies.spec.ts index a7cfa4fa89df..ac05708ff120 100644 --- a/openmetadata-ui/src/main/resources/ui/playwright/e2e/Pages/Policies.spec.ts +++ b/openmetadata-ui/src/main/resources/ui/playwright/e2e/Pages/Policies.spec.ts @@ -54,8 +54,7 @@ const addRule = async ( ruleDescription: string, descriptionIndex: number ) => { - // Validate form name field input - await page.fill('[data-testid="rule-name"]', rulename); + await page.getByTestId('rule-name').getByRole('textbox').fill(rulename); // Enter rule description await page @@ -63,36 +62,25 @@ const addRule = async ( .nth(descriptionIndex) .fill(ruleDescription); - // Select resource dropdown - await page.locator('[data-testid="resources"]').click(); + // Select "All" resources, then the first operation for them + const resources = page.getByTestId('resources'); + await resources.getByRole('combobox').click(); + await page.getByRole('option', { name: 'All', exact: true }).click(); + await resources.getByRole('combobox').press('Escape'); - // Select All - await page.locator('.ant-select-tree-checkbox-inner').first().click(); + const operations = page.getByTestId('operations'); + await operations.getByRole('combobox').click(); + await page.getByRole('option', { name: 'All', exact: true }).click(); + await operations.getByRole('combobox').press('Escape'); - // Click on operations dropdown - await page.locator('[data-testid="operations"]').click(); - - // Select operation - await page.locator('.ant-select-tree-checkbox-inner').nth(1).click(); - - // Click on condition combobox - await page.locator('[data-testid="condition"]').click(); - - // Select condition. The listener goes up first because typing the condition validates it too, - // so the response can arrive before the option is clicked. + // Typing the condition validates it, so the listener goes up first. const conditionResponse = page.waitForResponse( '/api/v1/policies/validation/condition/*' ); - - // Type the condition to filter the list. The options are built from every policy function's - // examples and the dropdown is virtualized, so an option far enough down the list is not in the - // DOM to be clicked - which is what happens whenever a new function is added. await page - .locator( - '[data-testid="condition"] > .ant-select-selector .ant-select-selection-search-input' - ) + .getByTestId('condition') + .getByRole('combobox') .fill(RULE_DETAILS.condition); - await page.locator(`[title="${RULE_DETAILS.condition}"]`).click(); await conditionResponse; // Verify condition success @@ -137,7 +125,7 @@ test.describe( ).toBeVisible(); // Enter policy name - await page.locator('[data-testid="policy-name"]').fill(POLICY_NAME); + await page.getByTestId('policy-name').fill(POLICY_NAME); await validateFormNameFieldInput({ page, value: POLICY_NAME, @@ -268,7 +256,10 @@ test.describe( await page.locator('[data-testid="edit-rule"]').click(); // Enter new name - await page.locator('[data-testid="rule-name"]').fill(UPDATED_RULE_NAME); + await page + .getByTestId('rule-name') + .getByRole('textbox') + .fill(UPDATED_RULE_NAME); // Click on submit button await page.locator('[data-testid="submit-btn"]').click(); diff --git a/openmetadata-ui/src/main/resources/ui/playwright/e2e/Pages/Roles.spec.ts b/openmetadata-ui/src/main/resources/ui/playwright/e2e/Pages/Roles.spec.ts index 77cd7ebe23a0..2b9093658d52 100644 --- a/openmetadata-ui/src/main/resources/ui/playwright/e2e/Pages/Roles.spec.ts +++ b/openmetadata-ui/src/main/resources/ui/playwright/e2e/Pages/Roles.spec.ts @@ -91,30 +91,19 @@ test.describe('Roles page tests', PLAYWRIGHT_BASIC_TEST_TAG_OBJ, () => { await descriptionField.fill(description); // Select the policies - search and select from dropdown - const policiesDropdown = page.locator('[data-testid="policies"]'); - await expect(policiesDropdown).toBeVisible(); - await policiesDropdown.click(); - await policiesDropdown.locator('input').fill('Data'); - const dataConsumerOption = page - .locator('.ant-select-dropdown:visible') - .locator('[title="Data Consumer Policy"]'); - await expect(dataConsumerOption).toBeVisible(); - await dataConsumerOption.click(); - - await policiesDropdown.locator('input').fill('Data'); - const dataStewardOption = page - .locator('.ant-select-dropdown:visible') - .locator('[title="Data Steward Policy"]'); - await expect(dataStewardOption).toBeVisible(); - await dataStewardOption.click(); - - // close dropdown by clicking side panel - await page.getByText('Add Role').click(); - - // Wait for dropdown to close - await expect( - page.locator('.ant-select-dropdown:visible') - ).not.toBeVisible(); + const policiesInput = page.getByTestId('policies').getByRole('combobox'); + await policiesInput.fill('Data'); + await page + .getByRole('option', { name: 'Data Consumer Policy', exact: true }) + .click(); + await policiesInput.fill('Data'); + await page + .getByRole('option', { name: 'Data Steward Policy', exact: true }) + .click(); + + // Close the list so it does not cover the footer + await policiesInput.press('Escape'); + await expect(page.getByRole('listbox')).not.toBeVisible(); // Save the role - wait for API response and UI update const submitButton = page.locator('[data-testid="submit-btn"]'); @@ -262,12 +251,10 @@ test.describe('Roles page tests', PLAYWRIGHT_BASIC_TEST_TAG_OBJ, () => { await expect(submitButton).toBeEnabled(); await submitButton.click(); - // Verify the error message that is displayed - wait for alert to appear - const errorAlert = page.locator('[role="alert"]'); - await expect(errorAlert).toBeVisible(); - await expect(errorAlert).toContainText( - errorMessageValidation.ifPolicyNotSelected - ); + // Verify the validation message under the policies field + await expect( + page.getByText(errorMessageValidation.ifPolicyNotSelected) + ).toBeVisible(); }); await test.step('Edit created role', async () => { diff --git a/openmetadata-ui/src/main/resources/ui/playwright/e2e/Pages/SearchSettings.spec.ts b/openmetadata-ui/src/main/resources/ui/playwright/e2e/Pages/SearchSettings.spec.ts index a12b4d5378c2..1f80bd74fa50 100644 --- a/openmetadata-ui/src/main/resources/ui/playwright/e2e/Pages/SearchSettings.spec.ts +++ b/openmetadata-ui/src/main/resources/ui/playwright/e2e/Pages/SearchSettings.spec.ts @@ -28,6 +28,7 @@ import { mockEntitySearchSettings, openMatchingFieldsPanel, restoreDefaultSearchSettings, + selectSettingOption, setSliderValue, } from '../../utils/searchSettingUtils'; import { settingClick } from '../../utils/sidebar'; @@ -147,24 +148,21 @@ test.describe('Search Settings', () => { // Add and remove field value boost await page.getByTestId('add-field-value-boost-btn').click(); - const modal = page.getByTestId('field-value-boost-modal'); + const modal = page.getByTestId('field-value-boost-dialog'); await modal.waitFor({ state: 'visible' }); - await modal.locator('#field').click(); - - await page - .locator('.ant-select-dropdown:visible') - .getByTitle('totalVotes') - .click(); + await selectSettingOption( + page, + modal.getByTestId('field-select'), + 'totalVotes' + ); - await page - .locator('.ant-select-dropdown:visible') - .waitFor({ state: 'hidden' }); + await modal.getByRole('slider').fill('25.6'); - await setSliderValue(page, 'field-boost-slider', 25.6); + await expect(modal.getByTestId('field-boost-value')).toHaveText('25.6'); - await modal.getByRole('button', { name: 'Save' }).click(); + await modal.getByTestId('save-field-value-boost').click(); await toastNotification(page, /Search Settings updated successfully/); @@ -264,35 +262,23 @@ test.describe('Search Settings', () => { // Match Type const matchTypeSelect = page.getByTestId('match-type-select'); - await matchTypeSelect.click(); - await page - .locator('.ant-select-dropdown:visible') - .getByTitle('Fuzzy Match') - .click(); + await selectSettingOption(page, matchTypeSelect, 'Fuzzy Match'); // Score Mode const scoreModeSelect = page.getByTestId('score-mode-select'); - await scoreModeSelect.click(); - await page - .locator('.ant-select-dropdown:visible') - .getByTitle('Max') - .click(); + await selectSettingOption(page, scoreModeSelect, 'Max'); // Boost Mode const boostModeSelect = page.getByTestId('boost-mode-select'); - await boostModeSelect.click(); - await page - .locator('.ant-select-dropdown:visible') - .getByTitle('Replace') - .click(); + await selectSettingOption(page, boostModeSelect, 'Replace'); // Save await page.getByTestId('save-btn').click(); await toastNotification(page, /Search Settings updated successfully/); - await expect(scoreModeSelect).toHaveText('Max'); - await expect(boostModeSelect).toHaveText('Replace'); + await expect(scoreModeSelect.getByRole('button')).toHaveText('Max'); + await expect(boostModeSelect.getByRole('button')).toHaveText('Replace'); }); test('Highlight toggle is offered for analyzed fields', async ({ @@ -816,11 +802,7 @@ test.describe('Search Settings', () => { ); const matchTypeSelect = page.getByTestId('match-type-select'); - await matchTypeSelect.click(); - await page - .locator('.ant-select-dropdown:visible') - .getByTitle('Exact Match') - .click(); + await selectSettingOption(page, matchTypeSelect, 'Exact Match'); const saveSettings = page.waitForResponse( (response) => diff --git a/openmetadata-ui/src/main/resources/ui/playwright/e2e/Pages/TaskFormSettings.spec.ts b/openmetadata-ui/src/main/resources/ui/playwright/e2e/Pages/TaskFormSettings.spec.ts index 771401cfebec..199aab7d0245 100644 --- a/openmetadata-ui/src/main/resources/ui/playwright/e2e/Pages/TaskFormSettings.spec.ts +++ b/openmetadata-ui/src/main/resources/ui/playwright/e2e/Pages/TaskFormSettings.spec.ts @@ -29,18 +29,20 @@ import { authenticateAdminPage, createAdminApiContext, } from '../../utils/admin'; -import { toastNotification, uuid } from '../../utils/common'; +import { + selectOptionWithRetry, + toastNotification, + uuid, +} from '../../utils/common'; import { waitForResponseWithStatus } from '../../utils/waitHelpers'; const TASK_FORM_SETTINGS_ROUTE = '/settings/governance/task-forms'; -const selectAntOption = async (page: Page, testId: string, option: string) => { - await page.getByTestId(testId).click(); - await page - .locator('.ant-select-dropdown .ant-select-item-option-content') - .filter({ hasText: option }) - .first() - .click(); +const selectOption = async (page: Page, testId: string, option: string) => { + await selectOptionWithRetry( + page.getByTestId(testId).getByRole('button'), + page.getByRole('option', { name: option, exact: true }) + ); }; test.describe.serial('Task Form Settings', () => { @@ -89,7 +91,7 @@ test.describe.serial('Task Form Settings', () => { await page.getByTestId('task-form-add-button').click(); await page.getByTestId('task-form-name-input').fill(schemaName); await page.getByTestId('task-form-type-input').fill(taskType); - await selectAntOption(page, 'task-form-category-input', 'Custom'); + await selectOption(page, 'task-form-category-input', 'Custom'); await page.getByRole('tab', { name: 'Create Form' }).click(); await page.getByTestId('task-form-create-builder-add-field').click(); @@ -116,7 +118,7 @@ test.describe.serial('Task Form Settings', () => { await page.getByRole('tab', { name: 'Workflow Stages' }).click(); await page.getByTestId('task-form-stage-mapping-add-button').click(); await page.getByTestId('task-form-stage-id-0').fill('open'); - await selectAntOption(page, 'task-form-stage-status-0', 'Open'); + await selectOption(page, 'task-form-stage-status-0', 'Open'); const createResponse = waitForResponseWithStatus( page, diff --git a/openmetadata-ui/src/main/resources/ui/playwright/e2e/Pages/UserDetails.spec.ts b/openmetadata-ui/src/main/resources/ui/playwright/e2e/Pages/UserDetails.spec.ts index 235552753f1f..8844abcd8296 100644 --- a/openmetadata-ui/src/main/resources/ui/playwright/e2e/Pages/UserDetails.spec.ts +++ b/openmetadata-ui/src/main/resources/ui/playwright/e2e/Pages/UserDetails.spec.ts @@ -466,32 +466,23 @@ test.describe('User with different Roles', () => { adminPage.getByTestId('profile-edit-roles-select') ).toBeVisible(); - await adminPage.locator('.ant-select-dropdown').waitFor({ - state: 'visible', - }); + await adminPage.getByRole('listbox').waitFor({ state: 'visible' }); const applicationRolesResponse = adminPage.waitForResponse( '/api/v1/roles/search?*' ); await adminPage .getByTestId('profile-edit-roles-select') - .locator('input') + .getByRole('combobox') .fill('Application'); await applicationRolesResponse; await adminPage - .locator('.ant-select-item-option-content') - .getByText('Application bot role', { exact: true }) - .waitFor({ state: 'visible' }); - await adminPage - .locator('.ant-select-item-option-content') - .getByText('Application bot role', { exact: true }) + .getByRole('option', { name: 'Application bot role', exact: true }) .click(); await adminPage.keyboard.press('Escape'); - await adminPage.locator('.ant-select-dropdown').waitFor({ - state: 'hidden', - }); + await adminPage.getByRole('listbox').waitFor({ state: 'hidden' }); const saveTeamsResponse = adminPage.waitForResponse( (response) => diff --git a/openmetadata-ui/src/main/resources/ui/playwright/e2e/Pages/Users.spec.ts b/openmetadata-ui/src/main/resources/ui/playwright/e2e/Pages/Users.spec.ts index 002d45f4bc8a..79bbec06a7df 100644 --- a/openmetadata-ui/src/main/resources/ui/playwright/e2e/Pages/Users.spec.ts +++ b/openmetadata-ui/src/main/resources/ui/playwright/e2e/Pages/Users.spec.ts @@ -945,12 +945,21 @@ test.describe('User Profile Dropdown Persona Interactions', () => { .click(); await adminPage.getByTestId('default-persona-select-list').waitFor(); - await adminPage.locator('.ant-select-dropdown').waitFor({ - state: 'visible', - }); + await adminPage + .getByTestId('default-persona-select-list') + .getByTestId('autocomplete-selected-item') + .getByRole('button') + .click(); + await adminPage + .getByTestId('default-persona-select-list') + .getByRole('combobox') + .click(); + await adminPage.getByRole('listbox').waitFor({ state: 'visible' }); // Select the second persona as default - await adminPage.getByTitle(persona2.data.displayName).click(); + await adminPage + .getByRole('option', { name: persona2.data.displayName }) + .click(); const defaultPersonaChangeResponse = adminPage.waitForResponse('/api/v1/users/*'); @@ -1011,9 +1020,17 @@ test.describe('User Profile Dropdown Persona Interactions', () => { .click(); await adminPage.getByTestId('default-persona-select-list').waitFor(); await adminPage - .locator('[data-testid="default-persona-select-list"] .ant-select-clear') + .getByTestId('default-persona-select-list') + .getByTestId('autocomplete-selected-item') + .getByRole('button') .click(); + // Removing the chip refocuses the field, which reopens the list over Save. + await adminPage + .getByTestId('default-persona-select-list') + .getByRole('combobox') + .press('Escape'); + const removePersonaResponse = adminPage.waitForResponse('/api/v1/users/*'); await adminPage .locator('[data-testid="user-profile-default-persona-edit-save"]') @@ -1149,9 +1166,25 @@ test.describe('User Profile Persona Interactions', () => { await adminPage.getByTestId('persona-select-list').waitFor(); // Clear all selected personas - await adminPage - .locator('[data-testid="persona-select-list"] .ant-select-clear') - .click(); + const personaSelect = adminPage.getByTestId('persona-select-list'); + const selectedPersonaChips = personaSelect.getByTestId( + 'autocomplete-selected-item' + ); + // The open list hides the chips from the accessibility tree, and each + // removal refocuses the field and reopens it. + const closePersonaList = () => + personaSelect.getByRole('combobox').press('Escape'); + await closePersonaList(); + // Only the first few chips render at once, so re-read after each removal. + let [chipName] = await selectedPersonaChips.allTextContents(); + while (chipName) { + await selectedPersonaChips + .filter({ hasText: chipName }) + .getByRole('button') + .click(); + await closePersonaList(); + [chipName] = await selectedPersonaChips.allTextContents(); + } const updateUserPromise = adminPage.waitForResponse( (response) => @@ -1197,16 +1230,11 @@ test.describe('User Profile Persona Interactions', () => { await adminPage.getByTestId('default-persona-select-list').waitFor(); // Wait for dropdown to open and options to load - await adminPage.locator('.ant-select-dropdown').waitFor({ - state: 'visible', - }); + await adminPage.getByRole('listbox').waitFor({ state: 'visible' }); - // Select specific persona for default - try test ID first, fallback to role selector - const defaultPersonaOptionTestId = adminPage.getByTitle( - persona1.responseData.displayName - ); - - await defaultPersonaOptionTestId.click(); + await adminPage + .getByRole('option', { name: persona1.responseData.displayName }) + .click(); // Save the changes const savePersonaResponse = adminPage.waitForResponse('/api/v1/users/*'); @@ -1275,11 +1303,17 @@ test.describe('User Profile Persona Interactions', () => { // Clear the selected default persona await adminPage - .locator( - '[data-testid="default-persona-select-list"] .ant-select-clear' - ) + .getByTestId('default-persona-select-list') + .getByTestId('autocomplete-selected-item') + .getByRole('button') .click(); + // Removing the chip refocuses the field, which reopens the list over Save. + await adminPage + .getByTestId('default-persona-select-list') + .getByRole('combobox') + .press('Escape'); + const defaultPersonaChangeResponse = adminPage.waitForResponse('/api/v1/users/*'); diff --git a/openmetadata-ui/src/main/resources/ui/playwright/e2e/VisualRegression/__snapshots__/aiModePages.spec.ts-snapshots/ai-explore.png b/openmetadata-ui/src/main/resources/ui/playwright/e2e/VisualRegression/__snapshots__/aiModePages.spec.ts-snapshots/ai-explore.png index 354e34cd5dd9..a9cbab8383c8 100644 Binary files a/openmetadata-ui/src/main/resources/ui/playwright/e2e/VisualRegression/__snapshots__/aiModePages.spec.ts-snapshots/ai-explore.png and b/openmetadata-ui/src/main/resources/ui/playwright/e2e/VisualRegression/__snapshots__/aiModePages.spec.ts-snapshots/ai-explore.png differ diff --git a/openmetadata-ui/src/main/resources/ui/playwright/e2e/VisualRegression/__snapshots__/metricList.spec.ts-snapshots/metric-list-narrow.png b/openmetadata-ui/src/main/resources/ui/playwright/e2e/VisualRegression/__snapshots__/metricList.spec.ts-snapshots/metric-list-narrow.png index ab0959e3082e..9e0e4699a02f 100644 Binary files a/openmetadata-ui/src/main/resources/ui/playwright/e2e/VisualRegression/__snapshots__/metricList.spec.ts-snapshots/metric-list-narrow.png and b/openmetadata-ui/src/main/resources/ui/playwright/e2e/VisualRegression/__snapshots__/metricList.spec.ts-snapshots/metric-list-narrow.png differ diff --git a/openmetadata-ui/src/main/resources/ui/playwright/e2e/VisualRegression/__snapshots__/staticPages.spec.ts-snapshots/glossary.png b/openmetadata-ui/src/main/resources/ui/playwright/e2e/VisualRegression/__snapshots__/staticPages.spec.ts-snapshots/glossary.png index 5dbc87b207bf..5f765029840d 100644 Binary files a/openmetadata-ui/src/main/resources/ui/playwright/e2e/VisualRegression/__snapshots__/staticPages.spec.ts-snapshots/glossary.png and b/openmetadata-ui/src/main/resources/ui/playwright/e2e/VisualRegression/__snapshots__/staticPages.spec.ts-snapshots/glossary.png differ diff --git a/openmetadata-ui/src/main/resources/ui/playwright/e2e/VisualRegression/__snapshots__/staticPages.spec.ts-snapshots/landing-page-sidebar-collapsed.png b/openmetadata-ui/src/main/resources/ui/playwright/e2e/VisualRegression/__snapshots__/staticPages.spec.ts-snapshots/landing-page-sidebar-collapsed.png index 37e655059ba4..ff56d0d97a9c 100644 Binary files a/openmetadata-ui/src/main/resources/ui/playwright/e2e/VisualRegression/__snapshots__/staticPages.spec.ts-snapshots/landing-page-sidebar-collapsed.png and b/openmetadata-ui/src/main/resources/ui/playwright/e2e/VisualRegression/__snapshots__/staticPages.spec.ts-snapshots/landing-page-sidebar-collapsed.png differ diff --git a/openmetadata-ui/src/main/resources/ui/playwright/e2e/VisualRegression/__snapshots__/staticPages.spec.ts-snapshots/landing-page.png b/openmetadata-ui/src/main/resources/ui/playwright/e2e/VisualRegression/__snapshots__/staticPages.spec.ts-snapshots/landing-page.png index 9e8b12145639..c6639ea154ad 100644 Binary files a/openmetadata-ui/src/main/resources/ui/playwright/e2e/VisualRegression/__snapshots__/staticPages.spec.ts-snapshots/landing-page.png and b/openmetadata-ui/src/main/resources/ui/playwright/e2e/VisualRegression/__snapshots__/staticPages.spec.ts-snapshots/landing-page.png differ diff --git a/openmetadata-ui/src/main/resources/ui/playwright/e2e/VisualRegression/__snapshots__/staticPages.spec.ts-snapshots/settings.png b/openmetadata-ui/src/main/resources/ui/playwright/e2e/VisualRegression/__snapshots__/staticPages.spec.ts-snapshots/settings.png index 653dcc1babb4..61cf725acb6c 100644 Binary files a/openmetadata-ui/src/main/resources/ui/playwright/e2e/VisualRegression/__snapshots__/staticPages.spec.ts-snapshots/settings.png and b/openmetadata-ui/src/main/resources/ui/playwright/e2e/VisualRegression/__snapshots__/staticPages.spec.ts-snapshots/settings.png differ diff --git a/openmetadata-ui/src/main/resources/ui/playwright/e2e/VisualRegression/__snapshots__/staticPages.spec.ts-snapshots/users.png b/openmetadata-ui/src/main/resources/ui/playwright/e2e/VisualRegression/__snapshots__/staticPages.spec.ts-snapshots/users.png index 17dafb720db2..387cfb54b073 100644 Binary files a/openmetadata-ui/src/main/resources/ui/playwright/e2e/VisualRegression/__snapshots__/staticPages.spec.ts-snapshots/users.png and b/openmetadata-ui/src/main/resources/ui/playwright/e2e/VisualRegression/__snapshots__/staticPages.spec.ts-snapshots/users.png differ diff --git a/openmetadata-ui/src/main/resources/ui/playwright/eslint-rules/tests/corpus.test.mjs b/openmetadata-ui/src/main/resources/ui/playwright/eslint-rules/tests/corpus.test.mjs index 2c31e7baa61e..3f448e257f36 100644 --- a/openmetadata-ui/src/main/resources/ui/playwright/eslint-rules/tests/corpus.test.mjs +++ b/openmetadata-ui/src/main/resources/ui/playwright/eslint-rules/tests/corpus.test.mjs @@ -61,8 +61,9 @@ test('the suppressions baseline matches its recorded state exactly', () => { // widgets they drove, taking six more with them, and the Data Contract // core-components migration removes three more. The Classic Observability // migration's core-component selectors in the Data Quality, Profiler and - // Profiler Configuration specs remove five more. - 'om-playwright/no-positional-locator': 573, + // Profiler Configuration specs remove five more. The antd Form / Select / + // Input migration (standalone controls, settings forms) removes five more. + 'om-playwright/no-positional-locator': 568, 'om-playwright/require-assertion-per-test': 1, 'playwright/no-skipped-test': 1, 'playwright/no-wait-for-selector': 14, diff --git a/openmetadata-ui/src/main/resources/ui/playwright/utils/bot.ts b/openmetadata-ui/src/main/resources/ui/playwright/utils/bot.ts index 750d7b10610c..a7331f0a1a2d 100644 --- a/openmetadata-ui/src/main/resources/ui/playwright/utils/bot.ts +++ b/openmetadata-ui/src/main/resources/ui/playwright/utils/bot.ts @@ -93,7 +93,7 @@ export const createBot = async (page: Page) => { // Select expiry time await page.click('[data-testid="token-expiry"]'); - await page.locator('[title="1 hour"] div').click(); + await page.getByRole('option', { name: '1 hour', exact: true }).click(); await fillDescriptionBox(page, BOT_DETAILS.description); @@ -199,7 +199,10 @@ export const tokenExpirationForDays = async (page: Page) => { // Select the expiration period await page - .locator(`text=${expiryTime} day${expiryTime > 1 ? 's' : ''}`) + .getByRole('option', { + name: `${expiryTime} day${expiryTime > 1 ? 's' : ''}`, + exact: true, + }) .click(); // Save the updated date @@ -249,7 +252,7 @@ export const verifyGenerateTokenAPIContract = async (page: Page) => { // Click on dropdown and select '7 days' expiry await page.click('[data-testid="token-expiry"]'); - await page.locator('text=7 days').click(); + await page.getByRole('option', { name: '7 days', exact: true }).click(); // Intercept the POST /api/v1/users/generateToken call const generateTokenResponsePromise = page.waitForResponse( @@ -299,7 +302,7 @@ export const tokenExpirationUnlimitedDays = async (page: Page) => { // Click on expiry token dropdown await page.click('[data-testid="token-expiry"]'); // Select unlimited days - await page.getByText('Unlimited').click(); + await page.getByRole('option', { name: 'Unlimited', exact: true }).click(); // Wait for the new generateToken API endpoint const generateTokenResponse = page.waitForResponse( (response) => @@ -361,7 +364,7 @@ export const resetTokenFromBotPage = async (page: Page, botName: string) => { await expect(page.getByTestId('token-expiry')).toBeVisible(); await page.getByTestId('token-expiry').click(); - await page.getByText('Unlimited').click(); + await page.getByRole('option', { name: 'Unlimited', exact: true }).click(); await expect(page.getByTestId('save-edit')).toBeVisible(); 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 01c97662f82b..4340026b1590 100644 --- a/openmetadata-ui/src/main/resources/ui/playwright/utils/customProperty.ts +++ b/openmetadata-ui/src/main/resources/ui/playwright/utils/customProperty.ts @@ -735,52 +735,64 @@ export const addCustomPropertiesForEntity = async ({ // Click the switch to show service doc panel await page.locator('[data-testid="show-side-panel-switch"]').click(); + const form = page.getByTestId('custom-property-form'); + const nameInput = form + .getByTestId('custom-property-name') + .getByRole('textbox'); + // Validation check — name must start with a letter/number and must not contain: " * : ^ $ \ < > & ~ / - await page.fill( - '[data-testid="name"]', - CUSTOM_PROPERTY_INVALID_NAMES.DISALLOWED_COLON - ); + await nameInput.fill(CUSTOM_PROPERTY_INVALID_NAMES.DISALLOWED_COLON); - await expect(page.locator('#name_help')).toContainText( - CUSTOM_PROPERTY_NAME_VALIDATION_ERROR - ); + await expect( + form.getByText(CUSTOM_PROPERTY_NAME_VALIDATION_ERROR) + ).toBeVisible(); // Correct name - await page.fill('[data-testid="name"]', propertyName); + await nameInput.fill(propertyName); // displayName - await page.fill('[data-testid="display-name"]', propertyName); + await form + .getByTestId('custom-property-display-name') + .getByRole('textbox') + .fill(propertyName); // Select custom type await selectOptionWithRetry( - page.locator('[data-testid="propertyType"]'), + form.getByTestId('custom-property-type').getByRole('button'), page.getByRole('option', { name: customType, exact: true }) ); // Enum configuration if (customType === 'Enum' && enumConfig) { + const enumInput = form + .getByTestId('custom-property-enum-config') + .locator('input'); for (const val of enumConfig.values) { - const enumInput = page.locator(String.raw`#root\/enumConfig`); - await enumInput.clear(); - await enumInput.pressSequentially(val, { delay: 50 }); + await enumInput.fill(val); await enumInput.press('Enter'); await expect(enumInput).toHaveValue(''); } + // The form is a dismissable drawer: Escape with no open list closes it. + if ((await enumInput.getAttribute('aria-expanded')) === 'true') { + await page.keyboard.press('Escape'); + } if (enumConfig.multiSelect) { - await page.getByTestId('multiSelect').click(); + await form.getByTestId('custom-property-multi-select').click(); } } // Table configuration if (customType === 'Table' && tableConfig) { + const columnInput = form + .getByTestId('custom-property-columns') + .locator('input'); for (const val of tableConfig.columns) { - const columnInput = page.locator(String.raw`#root\/columns`); - await expect(columnInput).toBeVisible(); - await columnInput.click(); - await columnInput.clear(); - await columnInput.pressSequentially(val, { delay: 100 }); + await columnInput.fill(val); await columnInput.press('Enter'); - await expect(columnInput).toHaveValue(''); // Verify input is consumed + await expect(columnInput).toHaveValue(''); + } + if ((await columnInput.getAttribute('aria-expanded')) === 'true') { + await page.keyboard.press('Escape'); } } @@ -789,54 +801,33 @@ export const addCustomPropertiesForEntity = async ({ ENTITY_REFERENCE_PROPERTIES.includes(customType) && entityReferenceConfig ) { + const refInput = form + .getByTestId('custom-property-entity-ref-config') + .locator('input'); for (const val of entityReferenceConfig) { - await page.click(String.raw`#root\/entityReferenceConfig`); - await page.keyboard.type(val); - - // CRITICAL: Use :visible selector chain pattern (Rule 4 from deflake guide) - const option = page - .locator('.ant-select-dropdown:visible') - .locator(`[title="${val}"]`); - await expect(option).toBeVisible(); - await option.click(); - - // Close the dropdown by pressing Escape + await refInput.click(); + await refInput.fill(val); + await page.getByRole('option', { name: val, exact: true }).click(); + } + if ((await refInput.getAttribute('aria-expanded')) === 'true') { await page.keyboard.press('Escape'); - - // Wait for dropdown to close - await expect(page.locator('.ant-select-dropdown')).toBeHidden(); - - // Verify the selection was applied - await expect( - page.locator(String.raw`#root\/entityReferenceConfig_list`) - ).not.toBeVisible(); } } // Format configuration if (['Date', 'Date Time', 'Time'].includes(customType) && formatConfig) { await selectOptionWithRetry( - page.getByTestId('formatConfig'), + form.getByTestId('custom-property-format-config').getByRole('button'), page.getByRole('option', { name: formatConfig, exact: true }) ); } // Description - await expect( - page.locator(String.raw`#root\/entityReferenceConfig_list`) - ).not.toBeVisible(); - await getDescriptionBox(page).waitFor({ state: 'visible' }); await expect(getDescriptionBox(page)).toHaveCount(1); await getDescriptionBox(page).click(); await page.keyboard.type(customPropertyData.description, { delay: 50 }); - // Click on name field to blur description and trigger validation without closing modal - await page.click('[data-testid="name"]'); - - await expect(page.locator('#propertyType_help')).not.toBeVisible(); - await expect(page.locator('#description_help')).not.toBeVisible(); - const createButton = page.locator('[data-testid="create-button"]'); await expect(createButton).toBeVisible(); const createPropertyPromise = page.waitForResponse( diff --git a/openmetadata-ui/src/main/resources/ui/playwright/utils/customizeLandingPage.ts b/openmetadata-ui/src/main/resources/ui/playwright/utils/customizeLandingPage.ts index 695f89c45acf..bf2f5a9a3473 100644 --- a/openmetadata-ui/src/main/resources/ui/playwright/utils/customizeLandingPage.ts +++ b/openmetadata-ui/src/main/resources/ui/playwright/utils/customizeLandingPage.ts @@ -387,8 +387,16 @@ export const setUserDefaultPersona = async ( const setDefaultPersona = page.waitForResponse('/api/v1/users/*'); - // Click on the persona option by text within the dropdown - await page.click(`.ant-select-dropdown:visible [title="${personaName}"]`); + const personaSelect = page.getByTestId('default-persona-select-list'); + const currentDefault = personaSelect.getByTestId( + 'autocomplete-selected-item' + ); + // Single select: an existing default is removed before picking another. + if ((await currentDefault.count()) > 0) { + await currentDefault.getByRole('button').click(); + } + await personaSelect.getByRole('combobox').click(); + await page.getByRole('option', { name: personaName }).click(); await page .locator('[data-testid="user-profile-default-persona-edit-save"]') diff --git a/openmetadata-ui/src/main/resources/ui/playwright/utils/dataInsight.ts b/openmetadata-ui/src/main/resources/ui/playwright/utils/dataInsight.ts index 4f281a64fa1d..3fee409cc308 100644 --- a/openmetadata-ui/src/main/resources/ui/playwright/utils/dataInsight.ts +++ b/openmetadata-ui/src/main/resources/ui/playwright/utils/dataInsight.ts @@ -14,7 +14,7 @@ import { APIRequestContext, Page } from '@playwright/test'; import { Kpi } from '../../src/generated/dataInsight/kpi/kpi'; import { KPIData } from '../constant/dataInsight.interface'; import { deleteFixtureEntity, okJson, settleAll } from './apiResponse'; -import { fillDescriptionBox } from './common'; +import { fillDescriptionBox, selectOptionWithRetry } from './common'; export const deleteKpiRequest = async ( apiRequest: APIRequestContext, @@ -77,12 +77,16 @@ export const addKpi = async (page: Page, data: KPIData) => { : currentDate.getDate(); const endDate = `${currentDate.getFullYear()}-${nextMonth}-${nextDate}`; - await page.click('#chartType'); - await page.click(`.ant-select-dropdown [title="${data.dataInsightChart}"]`); + await selectOptionWithRetry( + page.getByTestId('chartType').getByRole('button'), + page.getByRole('option', { name: data.dataInsightChart, exact: true }) + ); await page.getByTestId('displayName').fill(data.displayName); - await page.getByTestId('metricType').click(); - await page.click(`.ant-select-dropdown [title="${data.metricType}"]`); - await page.locator('.ant-slider-mark-text', { hasText: '100%' }).click(); + await selectOptionWithRetry( + page.getByTestId('metricType').getByRole('button'), + page.getByRole('option', { name: data.metricType, exact: true }) + ); + await page.getByRole('spinbutton').fill('100'); await page.getByTestId('start-date').click(); await page.getByTestId('start-date').fill(startDate); diff --git a/openmetadata-ui/src/main/resources/ui/playwright/utils/entityPanel.ts b/openmetadata-ui/src/main/resources/ui/playwright/utils/entityPanel.ts index f96cd695db5f..c7b22cda1f25 100644 --- a/openmetadata-ui/src/main/resources/ui/playwright/utils/entityPanel.ts +++ b/openmetadata-ui/src/main/resources/ui/playwright/utils/entityPanel.ts @@ -41,56 +41,18 @@ export const getEntityFqn = ( ).entityResponseData?.fullyQualifiedName; }; -const findOptionByScrolling = async (page: Page, endpoint: string) => { - const maxTries = 12; // Bound the scan; the list is ~25 options over ~3 viewports. - const filterName = ENDPOINT_TO_FILTER_MAP[endpoint]; - const dropdown = page - .getByTestId('global-search-select-dropdown') - .locator('.rc-virtual-list-holder'); - const option = page.getByTestId(`global-search-select-option-${filterName}`); - - // The filter list is virtualised, so only the ~11 rows around the current - // offset exist in the DOM: every option below "Container" — Stored Procedure, - // Data Product, API Endpoint, API Collection, Metric and the rest — is absent - // until scrolled into range. - // - // waitFor() rather than isVisible(): isVisible() resolves immediately and - // never retries, so probing it straight after scrollBy() races the re-render - // of the virtual window. That is why every entity type past the initial - // window was flaky under CI load while those inside it never were. When the - // option is already rendered this resolves instantly, so the timeout is only - // ever paid once per scroll step. - for (let tries = 0; tries < maxTries; tries++) { - const appeared = await option - .waitFor({ state: 'visible', timeout: 1_000 }) - .then(() => true) - .catch(() => false); - - if (appeared) { - await option.click(); - - return true; - } - - // Advance a whole viewport so the scan covers the list in a few steps, and - // detect the end from the clamped scrollTop rather than a fixed try count. - const movedBy = await dropdown.evaluate((element) => { - const before = element.scrollTop; - element.scrollBy(0, element.clientHeight); - - return element.scrollTop - before; - }); +const selectSearchIndexOption = async (page: Page, endpoint: string) => { + const option = page.getByTestId( + `global-search-select-option-${ENDPOINT_TO_FILTER_MAP[endpoint]}` + ); - if (movedBy === 0) { - break; // Already at the bottom: the option is not in this list. - } + // The core Select renders every option, so a missing one is genuinely absent. + if ((await option.count()) === 0) { + return false; } + await option.click(); - await dropdown.evaluate((element) => { - element.scrollTop = 0; - }); - - return false; + return true; }; export const openEntitySummaryPanel = async ({ @@ -136,10 +98,8 @@ export const openEntitySummaryPanel = async ({ state: 'visible', }); await page.getByTestId('global-search-selector').click(); - await page.getByTestId('global-search-select-dropdown').waitFor({ - state: 'visible', - }); - if (!(await findOptionByScrolling(page, endpoint))) { + await page.getByRole('listbox').waitFor({ state: 'visible' }); + if (!(await selectSearchIndexOption(page, endpoint))) { return false; } } diff --git a/openmetadata-ui/src/main/resources/ui/playwright/utils/lineage.ts b/openmetadata-ui/src/main/resources/ui/playwright/utils/lineage.ts index d4a1774a22e1..ff9789ebbcab 100644 --- a/openmetadata-ui/src/main/resources/ui/playwright/utils/lineage.ts +++ b/openmetadata-ui/src/main/resources/ui/playwright/utils/lineage.ts @@ -1117,8 +1117,10 @@ export const fillLineageConfigForm = async ( await page .getByTestId('field-downstream') .fill(config.downstreamDepth.toString()); - await page.getByTestId('field-lineage-layer').click(); - await page.locator(`.ant-select-item[title="${config.layer}"]`).click(); + await selectOptionWithRetry( + page.getByTestId('field-lineage-layer').getByRole('button'), + page.getByRole('option', { name: config.layer, exact: true }) + ); const saveRes = page.waitForResponse('/api/v1/system/settings'); await page.getByTestId('save-button').click(); @@ -1467,10 +1469,8 @@ export const verifyPlatformLineageForEntity = async ( await page .getByTestId('search-entity-select') - .locator('div') - .filter({ hasText: /^Search entity to view lineage$/ }) - .click(); - await page.getByTestId('search-entity-select').locator('input').fill(fromFqn); + .getByRole('combobox') + .fill(fromFqn); const focusSceneResponse = page.waitForResponse( (response) => diff --git a/openmetadata-ui/src/main/resources/ui/playwright/utils/navbar.ts b/openmetadata-ui/src/main/resources/ui/playwright/utils/navbar.ts index 2d1a4c5d2adc..dd2b28ad5ede 100644 --- a/openmetadata-ui/src/main/resources/ui/playwright/utils/navbar.ts +++ b/openmetadata-ui/src/main/resources/ui/playwright/utils/navbar.ts @@ -10,7 +10,7 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -import { expect, Locator, Page } from '@playwright/test'; +import { Locator, Page } from '@playwright/test'; export const navbarSearchItems = [ { @@ -112,39 +112,5 @@ export const selectOption = async ( await page.mouse.move(1280, 0); await dropdownLocator.click(); - await expect(page.locator('.ant-select-dropdown:visible')).toHaveCount(1); - - // Logic to scroll to find the option - // Since antd dropdown only ingests 10 option at a time in the DOM. - await page.getByTestId('global-search-select-dropdown').hover(); - - const maxScrollAttempts = 15; // Prevent infinite loop - let scrollAttempts = 0; - const scrollStep = 50; // Pixels to scroll each time - - while (scrollAttempts < maxScrollAttempts) { - // Check if option is visible in current view - const optionLocator = page.getByTestId( - `global-search-select-option-${optionTitle}` - ); - - const isOptionVisible = await optionLocator - .first() - .isVisible() - .catch(() => false); - - if (isOptionVisible) { - await optionLocator.first().click(); - - return; - } - - // Scroll down - await page.mouse.wheel(0, scrollStep); - - // eslint-disable-next-line playwright/no-wait-for-timeout -- DOM update delay after scroll - await page.waitForTimeout(100); - - scrollAttempts++; - } + await page.getByTestId(`global-search-select-option-${optionTitle}`).click(); }; diff --git a/openmetadata-ui/src/main/resources/ui/playwright/utils/searchSettingUtils.ts b/openmetadata-ui/src/main/resources/ui/playwright/utils/searchSettingUtils.ts index 1ef16e91417c..cf751dc4c417 100644 --- a/openmetadata-ui/src/main/resources/ui/playwright/utils/searchSettingUtils.ts +++ b/openmetadata-ui/src/main/resources/ui/playwright/utils/searchSettingUtils.ts @@ -10,7 +10,7 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -import { expect, Page } from '@playwright/test'; +import { expect, Locator, Page } from '@playwright/test'; import { getApiContext } from './common'; export const mockScoreMode = 'first'; @@ -190,3 +190,20 @@ export const restoreDefaultSearchSettings = async (page: Page) => { expect(tableConfig).toEqual(mockEntitySearchConfig); }; + +// Expanding a panel smooth-scrolls it, and react-aria closes a select popover +// whose trigger scrolls, so retry until the value sticks. +export const selectSettingOption = async ( + page: Page, + select: Locator, + optionName: string +) => { + const trigger = select.getByRole('button'); + await expect(async () => { + await trigger.click(); + await page.getByRole('option', { name: optionName }).click({ + timeout: 2_000, + }); + await expect(trigger).toHaveText(optionName, { timeout: 2_000 }); + }).toPass({ timeout: 20_000 }); +}; diff --git a/openmetadata-ui/src/main/resources/ui/playwright/utils/team.ts b/openmetadata-ui/src/main/resources/ui/playwright/utils/team.ts index 9946b3f40d42..e6130d8cc798 100644 --- a/openmetadata-ui/src/main/resources/ui/playwright/utils/team.ts +++ b/openmetadata-ui/src/main/resources/ui/playwright/utils/team.ts @@ -359,9 +359,10 @@ const checkTeamTypeOptions = async (page: Page, type: string) => { const teamTypeOptions = getTeamType(type)?.teamTypeOptions; if (teamTypeOptions) { for (const teamType of teamTypeOptions) { - const teamTypeElement = page.locator( - `.ant-select-dropdown [title="${teamType}"]` - ); + const teamTypeElement = page.getByRole('option', { + name: teamType, + exact: true, + }); await expect(teamTypeElement).toBeVisible(); } @@ -375,11 +376,13 @@ export const selectTeamHierarchy = async (page: Page, index: number) => { checkTeamTypeOptions(page, text); const childTeamType = getTeamType(text).childTeamType; await page - .locator(`.ant-select-dropdown [title="${childTeamType}"]`) + .getByRole('option', { name: childTeamType, exact: true }) .click(); } else { checkTeamTypeOptions(page, 'BusinessUnit'); - await page.locator(`.ant-select-dropdown [title='BusinessUnit']`).click(); + await page + .getByRole('option', { name: 'BusinessUnit', exact: true }) + .click(); } }; @@ -424,7 +427,9 @@ export const addTeamHierarchy = async ( if (isHierarchy) { await selectTeamHierarchy(page, index ?? 0); } else { - await page.click(`.ant-select-dropdown [title="${teamDetails.teamType}"]`); + await page + .getByRole('option', { name: teamDetails.teamType, exact: true }) + .click(); } // Under load ProseMirror can drop a fill, and the team is then created with diff --git a/openmetadata-ui/src/main/resources/ui/playwright/utils/teamSubscription.ts b/openmetadata-ui/src/main/resources/ui/playwright/utils/teamSubscription.ts index b3a9790a0af4..80bae51976d9 100644 --- a/openmetadata-ui/src/main/resources/ui/playwright/utils/teamSubscription.ts +++ b/openmetadata-ui/src/main/resources/ui/playwright/utils/teamSubscription.ts @@ -12,6 +12,7 @@ */ import { expect, Page } from '@playwright/test'; import { waitForAllLoadersToDisappear } from './entity'; +import { selectSettingOption } from './searchSettingUtils'; export type WebhookType = 'MS Teams' | 'Slack' | 'G Chat' | 'Webhook' | 'None'; export type WebhookIconTestId = @@ -34,21 +35,11 @@ export const selectWebhookType = async ( page: Page, webhookType: WebhookType ) => { - const webhookInput = page.locator('#webhook'); - const selectContainer = page - .getByTestId('subscription-modal') - .locator('.ant-select-selector') - .filter({ has: webhookInput }); - - await selectContainer.waitFor({ state: 'visible' }); - await selectContainer.click(); - - const dropdown = page.locator('.ant-select-dropdown:visible'); - const webhookOption = dropdown.getByText(webhookType, { exact: true }); - - await expect(webhookOption).toBeVisible(); - await webhookOption.click(); - await expect(dropdown).not.toBeVisible(); + await selectSettingOption( + page, + page.getByTestId('subscription-modal').getByTestId('webhook-select'), + webhookType + ); }; export const fillEndpointAndSave = async (page: Page, endpoint: string) => { diff --git a/openmetadata-ui/src/main/resources/ui/playwright/utils/user.ts b/openmetadata-ui/src/main/resources/ui/playwright/utils/user.ts index 5f1876c68ff4..5c3cb51f8c12 100644 --- a/openmetadata-ui/src/main/resources/ui/playwright/utils/user.ts +++ b/openmetadata-ui/src/main/resources/ui/playwright/utils/user.ts @@ -496,7 +496,7 @@ export const generateToken = async (page: Page) => { await page.click('[data-testid="token-expiry"]'); - await page.locator('[title="1 hour"] div').click(); + await page.getByRole('option', { name: '1 hour', exact: true }).click(); await expect(page.locator('[data-testid="token-expiry"]')).toBeVisible(); @@ -521,23 +521,18 @@ export const revokeToken = async (page: Page, isBot?: boolean) => { export const updateExpiration = async (page: Page, expiry: number) => { await page.click('[data-testid="token-expiry"]'); - await page.click(`text=${expiry} day${expiry > 1 ? 's' : ''}`); + await page + .getByRole('option', { + name: `${expiry} day${expiry > 1 ? 's' : ''}`, + exact: true, + }) + .click(); const expiryDate = customFormatDateTime( getEpochMillisForFutureDays(expiry as number), `ccc d'th' MMMM, yyyy` ); - // eslint-disable-next-line playwright/no-wait-for-timeout -- dropdown close animation delay - await page.waitForTimeout(100); - - // Click outside to close any open dropdowns - await page.mouse.click(1, 1); - - // Wait for any dropdown animations to complete - await page.locator('.ant-select-dropdown').waitFor({ state: 'hidden' }); - - // Now click the save button await page.click('[data-testid="save-edit"]'); await expect( @@ -731,9 +726,9 @@ export const addUser = async ( await fillDescriptionBox(page, 'Adding new user'); - await page.click(':nth-child(2) > .ant-radio > .ant-radio-input'); - await page.fill('input[name="password"]', password); - await page.fill('input[name="confirmPassword"]', password); + await page.getByTestId('password-create').click(); + await page.getByTestId('password').fill(password); + await page.getByTestId('confirmPassword').fill(password); const rolesCombobox = page .getByTestId('roles-dropdown') @@ -743,29 +738,19 @@ export const addUser = async ( const rolesSearchResponse = page.waitForResponse('/api/v1/roles/search?*'); await rolesCombobox.fill(role); await rolesSearchResponse; - const roleOption = page - .locator('.ant-select-item-option-content') - .filter({ hasText: new RegExp(`^${role}$`) }); + const roleOption = page.getByRole('option', { name: role, exact: true }); await expect(roleOption).toBeVisible({ timeout: LONG_ACTION_TIMEOUT }); await roleOption.click(); - await clickOutside(page); + await page.keyboard.press('Escape'); if (personas?.length) { - await page - .locator('[data-testid="personas-dropdown"] .ant-select-selector') - .click(); - await page + const personasCombobox = page .getByTestId('personas-dropdown') - .getByRole('combobox') - .fill(personas[0]); - await expect(page.locator('.ant-select-dropdown:visible')).toHaveCount(1); - const personaOption = page - .locator('.ant-select-dropdown:visible') - .locator('.ant-select-item-option') - .filter({ hasText: personas[0] }); - await personaOption.waitFor({ state: 'visible' }); - await personaOption.click(); - await clickOutside(page); + .getByRole('combobox'); + await personasCombobox.click(); + await personasCombobox.fill(personas[0]); + await page.getByRole('option', { name: personas[0], exact: true }).click(); + await page.keyboard.press('Escape'); } const saveResponse = page.waitForResponse('/api/v1/users'); @@ -795,9 +780,9 @@ export const checkForUserExistError = async ( await fillDescriptionBox(page, 'Adding new user'); - await page.click(':nth-child(2) > .ant-radio > .ant-radio-input'); - await page.fill('input[name="password"]', password); - await page.fill('input[name="confirmPassword"]', password); + await page.getByTestId('password-create').click(); + await page.getByTestId('password').fill(password); + await page.getByTestId('confirmPassword').fill(password); const saveResponse = page.waitForResponse('/api/v1/users'); await page.click('[data-testid="save-user"]'); diff --git a/openmetadata-ui/src/main/resources/ui/src/components/DataAssetRules/DataAssetRules.component.tsx b/openmetadata-ui/src/main/resources/ui/src/components/DataAssetRules/DataAssetRules.component.tsx index 7f2cbd10adb2..37adb4054ab9 100644 --- a/openmetadata-ui/src/main/resources/ui/src/components/DataAssetRules/DataAssetRules.component.tsx +++ b/openmetadata-ui/src/main/resources/ui/src/components/DataAssetRules/DataAssetRules.component.tsx @@ -15,15 +15,22 @@ import { PlusOutlined } from '@ant-design/icons'; import { Box, Button, + FormField, Grid, + HintText, + HookForm, + Input, SimpleModal, + TextArea, Toggle, Typography, } from '@openmetadata/ui-core-components'; -import { Form, Input, Table } from 'antd'; -import { FormInstance } from 'antd/es/form/Form'; +import { Registry } from '@rjsf/utils'; +import { Table } from 'antd'; import { AxiosError } from 'axios'; +import { noop } from 'lodash'; import React, { useCallback, useEffect, useMemo, useState } from 'react'; +import { useForm, UseFormReturn } from 'react-hook-form'; import { useTranslation } from 'react-i18next'; import { ReactComponent as AddPlaceHolderIcon } from '../../assets/svg/add-placeholder.svg'; import { EntityReferenceFields } from '../../enums/AdvancedSearch.enum'; @@ -124,11 +131,11 @@ export const useSemanticsRulesState = () => { export const SemanticsRuleForm: React.FC<{ semanticsRule: SemanticsRule; otherSemanticsRules: SemanticsRule[]; - form: FormInstance; + form: UseFormReturn; }> = ({ semanticsRule, otherSemanticsRules, form }) => { useEffect(() => { - form.setFieldsValue(semanticsRule); - }, [semanticsRule]); + form.reset(semanticsRule); + }, [semanticsRule, form]); const queryBuilderFields = useMemo(() => { const fields = jsonLogicSearchClassBase.getMapFields(); @@ -142,57 +149,82 @@ export const SemanticsRuleForm: React.FC<{ }; }, []); + const requiredRule = (field: string) => ({ + required: t('label.field-required', { field }), + }); + return ( -
- + { - if (otherSemanticsRules.some((rule) => rule.name === value)) { - return Promise.reject( - new Error(t('message.name-already-exists')) - ); - } - - return Promise.resolve(); - }, - }, - ]}> - - - + !otherSemanticsRules.some((rule) => rule.name === value) || + t('message.name-already-exists'), + }}> + {({ field, fieldState }) => ( + + )} + + - - - + {({ field, fieldState }) => ( +