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 0088b86f72e6..bb770bbdf6e6 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 @@ -10,12 +10,13 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -import { expect, test } from '@playwright/test'; +import { expect, Page, test as base } from '@playwright/test'; import { PLAYWRIGHT_BASIC_TEST_TAG_OBJ } from '../../constant/config'; import { GlobalSettingOptions } from '../../constant/settings'; import { TableClass } from '../../support/entity/TableClass'; +import { AdminClass } from '../../support/user/AdminClass'; +import { performAdminLogin } from '../../utils/admin'; import { - createNewPage, getApiContext, redirectToHomePage, toastNotification, @@ -29,303 +30,372 @@ import { } from '../../utils/searchSettingUtils'; import { settingClick } from '../../utils/sidebar'; -test.use({ storageState: 'playwright/.auth/admin.json' }); +let adminUser: AdminClass; + +// Using separate admin use fixture to avoid conflicts while asserting +// toast notifications for search settings update in tests. +const test = base.extend<{ page: Page }>({ + page: async ({ browser }, use) => { + const adminPage = await browser.newPage(); + await adminUser.login(adminPage); + await use(adminPage); + await adminPage.close(); + }, +}); + +test.describe('Search Settings', () => { + test.beforeAll(async ({ browser }) => { + adminUser = new AdminClass(); -test.describe('Search Settings Tests', PLAYWRIGHT_BASIC_TEST_TAG_OBJ, () => { - test.beforeEach(async ({ page }) => { - await redirectToHomePage(page); + const { apiContext, afterAction } = await performAdminLogin(browser); + await adminUser.create(apiContext); + await afterAction(); }); - test('Update global search settings', async ({ page }) => { - test.slow(true); + test.afterAll(async ({ browser }) => { + const { apiContext, afterAction } = await performAdminLogin(browser); + await adminUser.delete(apiContext); + await afterAction(); + }); - await settingClick(page, GlobalSettingOptions.SEARCH_SETTINGS); + test.describe('Search Settings Tests', PLAYWRIGHT_BASIC_TEST_TAG_OBJ, () => { + test.beforeEach(async ({ page }) => { + await redirectToHomePage(page); + }); - const enableRolesPolicesInSearchSwitch = page.getByTestId( - 'enable-roles-polices-in-search-switch' - ); + test('Update global search settings', async ({ page }) => { + await settingClick(page, GlobalSettingOptions.SEARCH_SETTINGS); - await enableRolesPolicesInSearchSwitch.click(); - await toastNotification(page, /Search Settings updated successfully/); + const enableRolesPolicesInSearchSwitch = page.getByTestId( + 'enable-roles-polices-in-search-switch' + ); - const globalSettingEditIcon = page.getByTestId( - 'global-setting-edit-icon-Max Aggregate Size' - ); - await globalSettingEditIcon.click(); + await enableRolesPolicesInSearchSwitch.click(); + await toastNotification(page, /Search Settings updated successfully/); - await page.getByTestId('value-input').fill('2000'); + // Add and remove field value boost + await page.getByTestId('add-field-value-boost-btn').click(); - await page.getByTestId('inline-save-btn').click(); - await toastNotification(page, /Search Settings updated successfully/); + await page + .locator('.field-value-boost-modal:visible') + .waitFor({ state: 'visible' }); - await expect( - page.getByTestId(`global-setting-value-Max Aggregate Size`) - ).toHaveText('2000'); - }); + const modal = page.locator('.field-value-boost-modal:visible'); - test('Update entity search settings', async ({ page }) => { - await settingClick(page, GlobalSettingOptions.SEARCH_SETTINGS); + await modal.locator('#field').click(); - const tableCard = page.getByTestId(mockEntitySearchSettings.key); + await page + .locator('.ant-select-dropdown:visible') + .getByTitle('totalVotes') + .click(); - await tableCard.click(); + await page + .locator('.ant-select-dropdown:visible') + .waitFor({ state: 'hidden' }); - await expect(page).toHaveURL( - new RegExp(mockEntitySearchSettings.url + '$') - ); + await setSliderValue(page, 'field-boost-slider', 25.6); - await expect( - page.getByTestId('entity-search-settings-header') - ).toBeVisible(); + await modal.locator('button').filter({ hasText: 'Save' }).click(); - const fieldContainers = page.getByTestId('field-container-header'); - const firstFieldContainer = fieldContainers.first(); - await firstFieldContainer.click(); + await toastNotification(page, /Search Settings updated successfully/); - // Highlight Field - const highlightFieldToggle = page.getByTestId('highlight-field-switch'); - await highlightFieldToggle.click(); + await page + .locator('.field-value-boost-panel') + .filter({ hasText: 'Field Value Boost' }) + .click(); - // Field Weight - await setSliderValue(page, 'field-weight-slider', 8); + await page + .locator('[data-row-key="totalVotes"]') + .getByTestId('delete-field-value-boost-btn') + .click(); - // Match Type - const matchTypeSelect = page.getByTestId('match-type-select'); - await matchTypeSelect.click(); - await page - .locator('.ant-select-dropdown:visible') - .getByTitle('Fuzzy Match') - .click(); + await toastNotification(page, /Search Settings updated successfully/); - // Score Mode - const scoreModeSelect = page.getByTestId('score-mode-select'); - await scoreModeSelect.click(); - await page - .locator('.ant-select-dropdown:visible') - .getByTitle('Max') - .click(); + const globalSettingEditIcon = page.getByTestId( + 'global-setting-edit-icon-Max Aggregate Size' + ); + await globalSettingEditIcon.click(); - // Boost Mode - const boostModeSelect = page.getByTestId('boost-mode-select'); - await boostModeSelect.click(); - await page - .locator('.ant-select-dropdown:visible') - .getByTitle('Replace') - .click(); + await page.getByTestId('value-input').fill('2000'); - // Save - await page.getByTestId('save-btn').click(); + await page.getByTestId('inline-save-btn').click(); + await toastNotification(page, /Search Settings updated successfully/); - await toastNotification(page, /Search Settings updated successfully/); + await expect( + page.getByTestId(`global-setting-value-Max Aggregate Size`) + ).toHaveText('2000'); + }); - await expect(scoreModeSelect).toHaveText('Max'); - await expect(boostModeSelect).toHaveText('Replace'); - }); + test('Update entity search settings', async ({ page }) => { + await settingClick(page, GlobalSettingOptions.SEARCH_SETTINGS); - test('Restore default search settings', async ({ page }) => { - await settingClick(page, GlobalSettingOptions.SEARCH_SETTINGS); + const tableCard = page.getByTestId(mockEntitySearchSettings.key); - const tableCard = page.getByTestId(mockEntitySearchSettings.key); + await tableCard.click(); - await tableCard.click(); + await expect(page).toHaveURL( + new RegExp(mockEntitySearchSettings.url + '$') + ); - await expect(page).toHaveURL( - new RegExp(mockEntitySearchSettings.url + '$') - ); + await expect( + page.getByTestId('entity-search-settings-header') + ).toBeVisible(); - const restoreDefaultsBtn = page.getByTestId('restore-defaults-btn'); - await restoreDefaultsBtn.click(); + const fieldContainers = page.getByTestId('field-container-header'); + const firstFieldContainer = fieldContainers.first(); + await firstFieldContainer.click(); - await restoreDefaultSearchSettings(page); + // Highlight Field + const highlightFieldToggle = page.getByTestId('highlight-field-switch'); + await highlightFieldToggle.click(); - await toastNotification(page, /Search Settings restored successfully/); - }); -}); + // Field Weight + await setSliderValue(page, 'field-weight-slider', 8); -test.describe('Search Preview test', () => { - const table1 = new TableClass(); - const table2 = new TableClass(); - // Override properties to include "ranking" keyword - table1.entity.name = `${table1.entity.name}-ranking`; - table1.entity.displayName = `${table1.entity.name}`; - table2.entity.description = `This is a ${table1.entity.name} test table for search settings verification`; - - test.beforeAll('Setup pre-requests', async ({ browser }) => { - const { apiContext, afterAction } = await createNewPage(browser); - // Create tables with the customized properties - await table1.create(apiContext); - await table2.create(apiContext); - await afterAction(); - }); + // Match Type + const matchTypeSelect = page.getByTestId('match-type-select'); + await matchTypeSelect.click(); + await page + .locator('.ant-select-dropdown:visible') + .getByTitle('Fuzzy Match') + .click(); - test.afterAll('Cleanup', async ({ browser }) => { - const { apiContext, afterAction } = await createNewPage(browser); - await table1.delete(apiContext); - await table2.delete(apiContext); - await afterAction(); - }); + // Score Mode + const scoreModeSelect = page.getByTestId('score-mode-select'); + await scoreModeSelect.click(); + await page + .locator('.ant-select-dropdown:visible') + .getByTitle('Max') + .click(); - test('Search preview for searchable table', async ({ page }) => { - await redirectToHomePage(page); - await settingClick(page, GlobalSettingOptions.SEARCH_SETTINGS); + // Boost Mode + const boostModeSelect = page.getByTestId('boost-mode-select'); + await boostModeSelect.click(); + await page + .locator('.ant-select-dropdown:visible') + .getByTitle('Replace') + .click(); - const tableCard = page.getByTestId(mockEntitySearchSettings.key); - await tableCard.click(); + // Save + await page.getByTestId('save-btn').click(); - await expect(page).toHaveURL( - new RegExp(mockEntitySearchSettings.url + '$') - ); + await toastNotification(page, /Search Settings updated successfully/); - await waitForAllLoadersToDisappear(page); + await expect(scoreModeSelect).toHaveText('Max'); + await expect(boostModeSelect).toHaveText('Replace'); + }); - const descriptionField = page.getByTestId( - `field-configuration-panel-description` - ); - await descriptionField.click(); - await setSliderValue(page, 'field-weight-slider', 68); + test('Restore default search settings', async ({ page }) => { + await settingClick(page, GlobalSettingOptions.SEARCH_SETTINGS); - const previewResponse = page.waitForResponse('/api/v1/search/preview'); - await page.getByTestId('highlight-field-switch').click(); - await previewResponse; + const tableCard = page.getByTestId(mockEntitySearchSettings.key); - await expect(page.getByTestId('highlight-field-switch')).toHaveAttribute( - 'aria-checked', - 'false' - ); + await tableCard.click(); - const searchInput = page.getByTestId('searchbar'); - await searchInput.fill(table1.entity.name); - await previewResponse; + await expect(page).toHaveURL( + new RegExp(mockEntitySearchSettings.url + '$') + ); - await waitForAllLoadersToDisappear(page); + const restoreDefaultsBtn = page.getByTestId('restore-defaults-btn'); + await restoreDefaultsBtn.click(); - const searchResultsContainer = page.locator('.search-results-container'); + await restoreDefaultSearchSettings(page); - // Get the search result cards - const searchCards = searchResultsContainer.locator('.search-card'); + await toastNotification(page, /Search Settings restored successfully/); + }); + }); - // Find the card where the title exactly matches the entity name - const matchedCard = searchCards.filter({ - has: page.getByTestId('entity-header-display-name').filter({ - hasText: table1.entity.name, - }), + test.describe('Search Preview test', () => { + const table1 = new TableClass(); + const table2 = new TableClass(); + // Override properties to include "ranking" keyword + table1.entity.name = `${table1.entity.name}-ranking`; + table1.entity.displayName = `${table1.entity.name}`; + table2.entity.description = `This is a ${table1.entity.name} test table for search settings verification`; + + test.beforeAll('Setup pre-requests', async ({ browser }) => { + const { apiContext, afterAction } = await performAdminLogin(browser); + // Create tables with the customized properties + await table1.create(apiContext); + await table2.create(apiContext); + await afterAction(); }); - // Assert that it exists - await expect(matchedCard).toHaveCount(1); + test.afterAll('Cleanup', async ({ browser }) => { + const { apiContext, afterAction } = await performAdminLogin(browser); + await table1.delete(apiContext); + await table2.delete(apiContext); + await afterAction(); + }); - // Optionally, check the description inside that card - await expect( - matchedCard.getByTestId('entity-header-display-name') - ).toHaveText(table1.entity.name); + test('Search preview for searchable table', async ({ page }) => { + await redirectToHomePage(page); + await settingClick(page, GlobalSettingOptions.SEARCH_SETTINGS); - // Find the card where the description matches table2's entity description - const cardWithDescription = searchCards.filter({ - has: page.getByTestId('description-text').filter({ - hasText: table2.entity.description, - }), - }); + const tableCard = page.getByTestId(mockEntitySearchSettings.key); + await tableCard.click(); - // Assert that such a card exists - await expect(cardWithDescription).toHaveCount(1); + await expect(page).toHaveURL( + new RegExp(mockEntitySearchSettings.url + '$') + ); - // Optionally, verify the description text - await expect( - cardWithDescription.getByTestId('description-text') - ).toHaveText(table2.entity.description); - }); -}); + await waitForAllLoadersToDisappear(page); -test.describe('Column Search Settings Tests', () => { - test.beforeEach(async ({ page }) => { - await redirectToHomePage(page); - }); + const descriptionField = page.getByTestId( + `field-configuration-panel-description` + ); + await descriptionField.click(); + await setSliderValue(page, 'field-weight-slider', 68); - test('Configure column search field settings', async ({ page }) => { - await settingClick(page, GlobalSettingOptions.SEARCH_SETTINGS); - - const columnCard = page.getByTestId('preferences.search-settings.column'); - await columnCard.click(); - - await expect(page).toHaveURL( - /settings\/preferences\/search-settings\/column$/ - ); - - const fieldContainers = page.getByTestId('field-container-header'); - const firstFieldContainer = fieldContainers.first(); - await firstFieldContainer.click(); - - const highlightToggle = page.getByTestId('highlight-field-switch'); - const wasHighlighted = - (await highlightToggle.getAttribute('aria-checked')) === 'true'; - await highlightToggle.click(); - - await setSliderValue(page, 'field-weight-slider', 15); - - const matchTypeSelect = page.getByTestId('match-type-select'); - await matchTypeSelect.click(); - await page - .locator('.ant-select-dropdown:visible') - .getByTitle('Exact Match') - .click(); - - const saveSettings = page.waitForResponse( - (response) => - response.url().includes('/api/v1/system/settings') && - response.request().method() === 'PUT' - ); - - await page.getByTestId('save-btn').click(); - await saveSettings; - - const previewResponse = page.waitForResponse('/api/v1/search/preview'); - await page.reload(); - await previewResponse; - await waitForAllLoadersToDisappear(page); - - await firstFieldContainer.click(); - await expect(highlightToggle).toHaveAttribute( - 'aria-checked', - String(!wasHighlighted) - ); - }); + const previewResponse = page.waitForResponse('/api/v1/search/preview'); + await page.getByTestId('highlight-field-switch').click(); + await previewResponse; - test('Search preview displays column results correctly', async ({ page }) => { - const { apiContext, afterAction } = await getApiContext(page); - const columnTable = new TableClass(); - const uniqueColumnName = `test_column_${uuid()}`; + await expect(page.getByTestId('highlight-field-switch')).toHaveAttribute( + 'aria-checked', + 'false' + ); - columnTable.entity.columns[0].name = uniqueColumnName; - columnTable.entity.columns[0].description = `Unique column for testing search preview`; + const searchInput = page.getByTestId('searchbar'); + await searchInput.fill(table1.entity.name); + await previewResponse; - try { - await columnTable.create(apiContext); + await waitForAllLoadersToDisappear(page); + + const searchResultsContainer = page.locator('.search-results-container'); + + // Get the search result cards + const searchCards = searchResultsContainer.locator('.search-card'); + + // Find the card where the title exactly matches the entity name + const matchedCard = searchCards.filter({ + has: page.getByTestId('entity-header-display-name').filter({ + hasText: table1.entity.name, + }), + }); + + // Assert that it exists + await expect(matchedCard).toHaveCount(1); + + // Optionally, check the description inside that card + await expect( + matchedCard.getByTestId('entity-header-display-name') + ).toHaveText(table1.entity.name); + + // Find the card where the description matches table2's entity description + const cardWithDescription = searchCards.filter({ + has: page.getByTestId('description-text').filter({ + hasText: table2.entity.description, + }), + }); + + // Assert that such a card exists + await expect(cardWithDescription).toHaveCount(1); + + // Optionally, verify the description text + await expect( + cardWithDescription.getByTestId('description-text') + ).toHaveText(table2.entity.description); + }); + }); + test.describe('Column Search Settings Tests', () => { + test.beforeEach(async ({ page }) => { await redirectToHomePage(page); + }); + + test('Configure column search field settings', async ({ page }) => { await settingClick(page, GlobalSettingOptions.SEARCH_SETTINGS); const columnCard = page.getByTestId('preferences.search-settings.column'); await columnCard.click(); - const searchInput = page.getByTestId('searchbar'); - await searchInput.fill(uniqueColumnName); + await expect(page).toHaveURL( + /settings\/preferences\/search-settings\/column$/ + ); + + const fieldContainers = page.getByTestId('field-container-header'); + const firstFieldContainer = fieldContainers.first(); + await firstFieldContainer.click(); + + const highlightToggle = page.getByTestId('highlight-field-switch'); + const wasHighlighted = + (await highlightToggle.getAttribute('aria-checked')) === 'true'; + await highlightToggle.click(); + + await setSliderValue(page, 'field-weight-slider', 15); + + const matchTypeSelect = page.getByTestId('match-type-select'); + await matchTypeSelect.click(); + await page + .locator('.ant-select-dropdown:visible') + .getByTitle('Exact Match') + .click(); + + const saveSettings = page.waitForResponse( + (response) => + response.url().includes('/api/v1/system/settings') && + response.request().method() === 'PUT' + ); + + await page.getByTestId('save-btn').click(); + await saveSettings; const previewResponse = page.waitForResponse('/api/v1/search/preview'); + await page.reload(); await previewResponse; + await waitForAllLoadersToDisappear(page); - const searchResultsContainer = page.locator('.search-results-container'); - const matchedCard = searchResultsContainer - .locator('.search-card') - .filter({ - has: page.getByTestId('entity-header-display-name').filter({ - hasText: uniqueColumnName, - }), - }); + await firstFieldContainer.click(); + await expect(highlightToggle).toHaveAttribute( + 'aria-checked', + String(!wasHighlighted) + ); + }); - await expect(matchedCard).toHaveCount(1); - } finally { - await columnTable.delete(apiContext); - await afterAction(); - } + test('Search preview displays column results correctly', async ({ + page, + }) => { + const { apiContext, afterAction } = await getApiContext(page); + const columnTable = new TableClass(); + const uniqueColumnName = `test_column_${uuid()}`; + + columnTable.entity.columns[0].name = uniqueColumnName; + columnTable.entity.columns[0].description = `Unique column for testing search preview`; + + try { + await columnTable.create(apiContext); + + await redirectToHomePage(page); + await settingClick(page, GlobalSettingOptions.SEARCH_SETTINGS); + + const columnCard = page.getByTestId( + 'preferences.search-settings.column' + ); + await columnCard.click(); + + const searchInput = page.getByTestId('searchbar'); + await searchInput.fill(uniqueColumnName); + + const previewResponse = page.waitForResponse('/api/v1/search/preview'); + await previewResponse; + + const searchResultsContainer = page.locator( + '.search-results-container' + ); + const matchedCard = searchResultsContainer + .locator('.search-card') + .filter({ + has: page.getByTestId('entity-header-display-name').filter({ + hasText: uniqueColumnName, + }), + }); + + await expect(matchedCard).toHaveCount(1); + } finally { + await columnTable.delete(apiContext); + await afterAction(); + } + }); }); }); 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 9bf110ca8698..055903820850 100644 --- a/openmetadata-ui/src/main/resources/ui/playwright/utils/searchSettingUtils.ts +++ b/openmetadata-ui/src/main/resources/ui/playwright/utils/searchSettingUtils.ts @@ -90,7 +90,7 @@ export async function setSliderValue( max = 100 ) { const sliderHandle = page.getByTestId(testId).locator('.ant-slider-handle'); - const sliderTrack = page.getByTestId(testId).locator('.ant-slider-track'); + const sliderTrack = page.getByTestId(testId).locator('.ant-slider-step'); // Get slider track dimensions const box = await sliderTrack.boundingBox(); diff --git a/openmetadata-ui/src/main/resources/ui/src/components/SearchSettings/FieldConfiguration/FieldConfiguration.tsx b/openmetadata-ui/src/main/resources/ui/src/components/SearchSettings/FieldConfiguration/FieldConfiguration.tsx index 40239713e26d..30098621e14a 100644 --- a/openmetadata-ui/src/main/resources/ui/src/components/SearchSettings/FieldConfiguration/FieldConfiguration.tsx +++ b/openmetadata-ui/src/main/resources/ui/src/components/SearchSettings/FieldConfiguration/FieldConfiguration.tsx @@ -178,6 +178,7 @@ const FieldConfiguration: React.FC = ({ = ({ = ({