Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
23 commits
Select commit Hold shift + click to select a range
dfd6fa9
chore(UI): upgrade react-router-dom v6 → v7 and fix column panel race…
Rohit0301 Jul 28, 2026
3531261
minor fix
Rohit0301 Jul 28, 2026
d006158
Merge branch 'main' into upgrade-react-router-dom-v7
Rohit0301 Jul 28, 2026
ac7e730
Merge branch 'main' into upgrade-react-router-dom-v7
Rohit0301 Jul 29, 2026
ddcc493
Merge branch 'main' into upgrade-react-router-dom-v7
Rohit0301 Jul 30, 2026
3607ca8
fix(UI): fix react-router v7 deferred navigation race conditions and …
Rohit0301 Aug 5, 2026
460b5a3
Merge branch 'main' into upgrade-react-router-dom-v7
Rohit0301 Aug 5, 2026
d8acb4f
Merge branch 'main' into upgrade-react-router-dom-v7
Rohit0301 Aug 5, 2026
641ecd4
Merge branch 'main' into upgrade-react-router-dom-v7
Rohit0301 Aug 7, 2026
319d20e
Merge branch 'main' into upgrade-react-router-dom-v7
Rohit0301 Aug 9, 2026
8f39917
Merge branch 'main' into upgrade-react-router-dom-v7
Rohit0301 Aug 12, 2026
3c6b610
playwright test fix
Rohit0301 Aug 12, 2026
8c01b44
minor fix
Rohit0301 Aug 12, 2026
abc1004
fixed playwright tests
Rohit0301 Aug 12, 2026
24e88bc
Merge branch 'main' into upgrade-react-router-dom-v7
Rohit0301 Aug 12, 2026
39cd1ac
Merge branch 'main' into upgrade-react-router-dom-v7
Rohit0301 Aug 13, 2026
02a0599
playwright fix
Rohit0301 Aug 13, 2026
18c2f6d
playwright fix
Rohit0301 Aug 13, 2026
98f84c8
minor fix
Rohit0301 Aug 14, 2026
7463147
minor fix
Rohit0301 Aug 15, 2026
3431846
Merge branch 'main' into upgrade-react-router-dom-v7
Rohit0301 Aug 15, 2026
bccf061
playwright fix
Rohit0301 Aug 15, 2026
0c71a98
Merge branch 'main' into upgrade-react-router-dom-v7
Rohit0301 Aug 15, 2026
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion openmetadata-ui/src/main/resources/ui/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -166,7 +166,7 @@
"react-quill-new": "^3.4.0",
"react-reflex": "^4.1.0",
"react-resizable": "^3.1.3",
"react-router-dom": "^6.30.4",
"react-router-dom": "7.18.1",
"reactflow": "^11.10.2",
"reactjs-localstorage": "^1.0.1",
"recharts": "2.10.3",
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -632,12 +632,15 @@
.getByTestId(`knowledge-card-${articleEntity.responseData.displayName}`);
await expect(viewedCard).toBeVisible();

const knowledgePageResponse = page.waitForResponse((response) =>
response.url().includes('/api/v1/contextCenter/pages/name/')
);
await viewedCard.getByTestId('knowledge-page-link').first().click();
await page.waitForURL((url) =>
url.pathname.includes('/context-center/articles/')
);
await knowledgePageResponse;
await waitForAllLoadersToDisappear(page);
await page.waitForTimeout(500);

await navigateToArticles(page);
const rightPanel = page.getByTestId('knowledge-center-right-panel');
Expand Down Expand Up @@ -1200,7 +1203,7 @@
.click();
await page.getByTestId('save').click();

await page.waitForSelector(

Check warning on line 1206 in openmetadata-ui/src/main/resources/ui/playwright/e2e/Features/ContextCenterArticles.spec.ts

View workflow job for this annotation

GitHub Actions / checkstyle

Unexpected use of page.waitForSelector()
'[role="dialog"].description-markdown-editor',
{ state: 'hidden' }
);
Expand Down Expand Up @@ -1455,7 +1458,7 @@
.getByTestId('entity-header-display-name')
.fill(newDisplayName);
await page.getByText('Unsaved').waitFor({ state: 'visible' });
await page.waitForTimeout(400);

Check warning on line 1461 in openmetadata-ui/src/main/resources/ui/playwright/e2e/Features/ContextCenterArticles.spec.ts

View workflow job for this annotation

GitHub Actions / checkstyle

Unexpected use of page.waitForTimeout()
await page.getByRole('link', { name: 'Articles' }).click();
});

Expand Down Expand Up @@ -1520,7 +1523,7 @@
await navigateToArticle(page, draftArticleA.fullyQualifiedName);
await page.fill('.om-block-editor', reloadDescription);
await page.getByText('Unsaved').waitFor({ state: 'visible' });
await page.waitForTimeout(400);

Check warning on line 1526 in openmetadata-ui/src/main/resources/ui/playwright/e2e/Features/ContextCenterArticles.spec.ts

View workflow job for this annotation

GitHub Actions / checkstyle

Unexpected use of page.waitForTimeout()
});

await test.step('Reload the page (simulates browser refresh before auto-save)', async () => {
Expand Down Expand Up @@ -1599,7 +1602,7 @@
await navigateToArticle(page, articleToDelete.fullyQualifiedName);
await page.fill('.om-block-editor', 'This draft should be deleted');
await page.getByText('Unsaved').waitFor({ state: 'visible' });
await page.waitForTimeout(400);

Check warning on line 1605 in openmetadata-ui/src/main/resources/ui/playwright/e2e/Features/ContextCenterArticles.spec.ts

View workflow job for this annotation

GitHub Actions / checkstyle

Unexpected use of page.waitForTimeout()
});

await test.step('Navigate away to ensure draft is persisted in localStorage', async () => {
Expand Down Expand Up @@ -1647,14 +1650,14 @@
await navigateToArticle(page, draftArticleA.fullyQualifiedName);
await page.fill('.om-block-editor', contentA);
await page.getByText('Unsaved').waitFor({ state: 'visible' });
await page.waitForTimeout(400);

Check warning on line 1653 in openmetadata-ui/src/main/resources/ui/playwright/e2e/Features/ContextCenterArticles.spec.ts

View workflow job for this annotation

GitHub Actions / checkstyle

Unexpected use of page.waitForTimeout()
});

await test.step('Navigate to draft article B and type without saving', async () => {
await navigateToArticle(page, draftArticleB.fullyQualifiedName);
await page.fill('.om-block-editor', contentB);
await page.getByText('Unsaved').waitFor({ state: 'visible' });
await page.waitForTimeout(400);

Check warning on line 1660 in openmetadata-ui/src/main/resources/ui/playwright/e2e/Features/ContextCenterArticles.spec.ts

View workflow job for this annotation

GitHub Actions / checkstyle

Unexpected use of page.waitForTimeout()
});

await test.step('Reload Article B — its own draft should be synced', async () => {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -43,6 +43,7 @@
validateLeftSidebarWithHiddenItems,
} from '../../utils/customizeNavigation';
import {
clickTabAndWaitForPanel,
getEncodedFqn,
waitForAllLoadersToDisappear,
} from '../../utils/entity';
Expand All @@ -60,13 +61,13 @@
userPage: Page;
}>({
adminPage: async ({ browser }, use) => {
const adminPage = await browser.newPage();

Check warning on line 64 in openmetadata-ui/src/main/resources/ui/playwright/e2e/Features/CustomizeDetailPage.spec.ts

View workflow job for this annotation

GitHub Actions / checkstyle

Prefer the `page` fixture (test.use({ storageState })) over browser.newPage() + manual login for single-user admin tests. For multi-user tests that need a second non-admin page, this warning is expected — no action needed
await adminUser.login(adminPage);
await use(adminPage);
await adminPage.close();
},
userPage: async ({ browser }, use) => {
const page = await browser.newPage();

Check warning on line 70 in openmetadata-ui/src/main/resources/ui/playwright/e2e/Features/CustomizeDetailPage.spec.ts

View workflow job for this annotation

GitHub Actions / checkstyle

Prefer the `page` fixture (test.use({ storageState })) over browser.newPage() + manual login for single-user admin tests. For multi-user tests that need a second non-admin page, this warning is expected — no action needed
await user.login(page);
await use(page);
await page.close();
Expand Down Expand Up @@ -498,7 +499,15 @@

await entity?.visitEntityPage(userPage);
await waitForAllLoadersToDisappear(userPage);

await waitForAllLoadersToDisappear(userPage, 'table-loading');
await waitForAllLoadersToDisappear(
userPage,
'entity-detail-widget-skeleton'
);
await userPage.waitForLoadState('domcontentloaded');
await userPage
.getByTestId('asset-description-container')
.waitFor({ state: 'visible' });
await expect(
userPage.getByRole('tab', { name: 'Custom Tab' })
).toBeVisible();
Expand All @@ -508,8 +517,18 @@
.last()
.getByRole('tab', { name: 'Custom Tab' });

await customTab.focus();
await userPage.keyboard.press('Enter');
// waitForLoadState('domcontentloaded') is a no-op on this SPA tab
// switch and guarded nothing against react-router v7's deferred
// navigate(); wait for the tab's own panel to become active instead.
await clickTabAndWaitForPanel(userPage, customTab);
await waitForAllLoadersToDisappear(
userPage,
'entity-detail-widget-skeleton'
);
await userPage
.getByTestId('asset-description-container')
.locator('visible=true')
.waitFor({ state: 'visible' });

await expect
.poll(async () =>
Expand Down Expand Up @@ -652,8 +671,15 @@

await entity?.visitEntityPage(userPage);
await waitForAllLoadersToDisappear(userPage);
await waitForAllLoadersToDisappear(userPage);

await waitForAllLoadersToDisappear(userPage, 'table-loading');
await waitForAllLoadersToDisappear(
userPage,
'entity-detail-widget-skeleton'
);
await userPage.waitForLoadState('domcontentloaded');
await userPage
.getByTestId('asset-description-container')
.waitFor({ state: 'visible' });
await expect(
userPage.getByRole('tab', { name: 'Custom Tab' })
).toBeVisible();
Expand All @@ -663,8 +689,18 @@
.last()
.getByRole('tab', { name: 'Custom Tab' });

await customTab.focus();
await userPage.keyboard.press('Enter');
// waitForLoadState('domcontentloaded') is a no-op on this SPA tab
// switch and guarded nothing against react-router v7's deferred
// navigate(); wait for the tab's own panel to become active instead.
await clickTabAndWaitForPanel(userPage, customTab);
await waitForAllLoadersToDisappear(
userPage,
'entity-detail-widget-skeleton'
);
await userPage
.getByTestId('asset-description-container')
.locator('visible=true')
.waitFor({ state: 'visible' });

await expect
.poll(async () =>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -12,9 +12,9 @@
*/
import {
APIRequestContext,
test as base,
expect,
Page,
test as base,
} from '@playwright/test';
import { ECustomizedGovernance } from '../../constant/customizeDetail';
import { GlobalSettingOptions } from '../../constant/settings';
Expand Down Expand Up @@ -42,13 +42,13 @@
const test = base.extend<{
adminPage: Page;
userPage: Page;
}>({

Check warning on line 45 in openmetadata-ui/src/main/resources/ui/playwright/e2e/Features/DataProductPersonaCustomization.spec.ts

View workflow job for this annotation

GitHub Actions / checkstyle

Prefer the `page` fixture (test.use({ storageState })) over browser.newPage() + manual login for single-user admin tests. For multi-user tests that need a second non-admin page, this warning is expected — no action needed
adminPage: async ({ browser }, use) => {
const adminPage = await browser.newPage();
await adminUser.login(adminPage);
await use(adminPage);
await adminPage.close();
},

Check warning on line 51 in openmetadata-ui/src/main/resources/ui/playwright/e2e/Features/DataProductPersonaCustomization.spec.ts

View workflow job for this annotation

GitHub Actions / checkstyle

Prefer the `page` fixture (test.use({ storageState })) over browser.newPage() + manual login for single-user admin tests. For multi-user tests that need a second non-admin page, this warning is expected — no action needed
userPage: async ({ browser }, use) => {
const page = await browser.newPage();
await user.login(page);
Expand Down Expand Up @@ -214,12 +214,18 @@

await entity?.visitEntityPage(userPage);
await waitForAllLoadersToDisappear(userPage);

await userPage.waitForLoadState('domcontentloaded');
await userPage.getByTestId('asset-description-container').waitFor({ state: 'visible' });
await expect(
userPage.getByRole('tab', { name: 'Custom Tab' })
).toBeVisible();


await userPage.getByRole('tab', { name: 'Custom Tab' }).click();

await waitForAllLoadersToDisappear(userPage);
await userPage.waitForLoadState('domcontentloaded');
await userPage.getByTestId('asset-description-container').locator('visible=true').waitFor({ state: 'visible' });

const visibleDescription = userPage
.getByTestId(/KnowledgePanel.Description-/)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,10 @@ import {
toastNotification,
uuid,
} from '../../../utils/common';
import { fillDeleteConfirmationIfPresent } from '../../../utils/entity';
import {
fillDeleteConfirmationIfPresent,
waitForAllLoadersToDisappear,
} from '../../../utils/entity';
import { findSystemTestDefinition } from '../../../utils/testCases';

const TEST_DEFINITION_NAME = `AaroCustomTestDefinition${uuid()}`;
Expand Down Expand Up @@ -1212,6 +1215,7 @@ test.describe(
);
await nextButton.click();
await fetchResponse;
await waitForAllLoadersToDisappear(page);

// Check again after page load
isItemVisible = await testDefLocator.isVisible();
Expand Down Expand Up @@ -1256,6 +1260,7 @@ test.describe(
expect(updateResponse.status()).toBe(200);

await toastNotification(page, /updated successfully/i);
await waitForAllLoadersToDisappear(page);

// Verify we stayed on the same page (previous button state should be unchanged)
if (prevDisabledBefore) {
Expand Down Expand Up @@ -1296,6 +1301,7 @@ test.describe(

// Wait for the GET that happens after delete (page reset + fetch)
await getResponse;
await waitForAllLoadersToDisappear(page);

await toastNotification(page, /deleted successfully/i);

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -1129,10 +1129,18 @@ test.describe('Impact Analysis', () => {

const downstreamCount = await page.locator('[data-row-key]').count();

const upstreamResponse = page.waitForResponse(
`/api/v1/lineage/getLineageByEntityCount?*`
);
await page.getByRole('radio', { name: 'Upstream' }).click();
await upstreamResponse;
await waitForAllLoadersToDisappear(page);

const downstreamResponse = page.waitForResponse(
`/api/v1/lineage/getLineageByEntityCount?*`
);
await page.getByRole('radio', { name: 'Downstream' }).click();
await downstreamResponse;
await waitForAllLoadersToDisappear(page);

const finalCount = await page.locator('[data-row-key]').count();
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -227,13 +227,27 @@ test.describe('Data Products', () => {
await test.step('Navigate to Data Products page', async () => {
await sidebarClick(page, SidebarItem.DATA_PRODUCT);
await waitForAllLoadersToDisappear(page);
await page.waitForLoadState('domcontentloaded');
});

await test.step('Search for specific data product', async () => {
// The listing page debounces search input by 300ms before firing
// /api/v1/search/query — waitForAllLoadersToDisappear alone doesn't
// guarantee that debounced request has even been sent yet, so assertions
// right after can still see the pre-search (unfiltered) list.
await page
.getByRole('main')
.getByPlaceholder('Search').waitFor({ state: 'visible' });
const searchResponse = page.waitForResponse(
(response) =>
response.url().includes('/api/v1/search/query') &&
response.url().includes(dataProduct1.data.name)
);
await page
.getByRole('main')
.getByPlaceholder('Search')
.fill(dataProduct1.data.name);
await searchResponse;
await waitForAllLoadersToDisappear(page);

await expect(page.getByText(dataProduct1.data.displayName)).toBeVisible();
Expand All @@ -243,7 +257,11 @@ test.describe('Data Products', () => {
});

await test.step('Clear search', async () => {
const clearResponse = page.waitForResponse((response) =>
response.url().includes('/api/v1/search/query')
);
await page.getByRole('main').getByPlaceholder('Search').clear();
await clearResponse;
await waitForAllLoadersToDisappear(page);

await expect(page.getByLabel('Pagination Navigation')).toBeVisible();
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -41,7 +41,10 @@ import {
verifyDescriptionRequiresScroll,
verifyEndOfDescriptionReachable,
} from '../../utils/domain';
import { waitForAllLoadersToDisappear } from '../../utils/entity';
import {
clickTabAndWaitForPanel,
waitForAllLoadersToDisappear,
} from '../../utils/entity';
import { navigateToPersonaWithPagination } from '../../utils/persona';
import { settingClick, sidebarClick } from '../../utils/sidebar';
import { test } from '../fixtures/pages';
Expand Down Expand Up @@ -409,15 +412,29 @@ test.describe(

await table.visitEntityPage(userPage);
await waitForAllLoadersToDisappear(userPage);

await expect(
userPage.getByRole('tab', { name: 'Description Tab' })
).toBeVisible();
await userPage.getByRole('tab', { name: 'Description Tab' }).click();
await userPage
.getByTestId('asset-description-container')
.waitFor({ state: 'visible' });
await userPage.waitForLoadState('domcontentloaded');
const descriptionTab = userPage.getByRole('tab', {
name: 'Description Tab',
});
await expect(descriptionTab).toBeVisible();
// waitForLoadState('domcontentloaded') is a no-op here — an SPA tab click
// triggers no real navigation, so it resolved instantly and guarded
// nothing against react-router v7's deferred navigate(). Wait for the
// active tabpanel instead so the old "Columns" tab content can't still be
// the thing this assertion matches against.
await clickTabAndWaitForPanel(userPage, descriptionTab);
await waitForAllLoadersToDisappear(
userPage,
'entity-detail-widget-skeleton'
);

const descriptionWidget = userPage
.getByTestId(/KnowledgePanel.Description-/)
.locator('visible=true');
await descriptionWidget.waitFor({ state: 'visible' });
await expect(descriptionWidget).toBeVisible();

// Widget truncates long content behind a "more" button
Expand All @@ -427,6 +444,17 @@ test.describe(
await expect(moreButton).toBeVisible();
await moreButton.click();

// The click flips readMore state, which swaps the BlockEditor (Tiptap)
// content prop from the truncated preview to the full text — but Tiptap
// applies content changes asynchronously (an effect-gated setTimeout,
// then a transaction event deferred behind two requestAnimationFrames),
// so the full text isn't guaranteed to be in the DOM immediately after
// the click. The button's own testid flipping to read-less-button is a
// direct signal that React has re-rendered with the expanded state.
await descriptionWidget
.getByTestId('read-less-button')
.waitFor({ state: 'visible' });

await verifyEndOfDescriptionReachable(descriptionWidget, userPage);

await userPage.close();
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2882,7 +2882,9 @@ test.describe('Glossary tests', () => {

// Open delete modal
await page.click('[data-testid="manage-button"]');
await page.click('[data-testid="delete-button"]');
const deleteButton = page.locator('[data-testid="delete-button"]');
await expect(deleteButton).toBeVisible();
await deleteButton.click();

// Verify delete modal is visible
await expect(page.locator('[role="dialog"]')).toBeVisible();
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -356,7 +356,9 @@ test.describe('Lineage Filters', () => {
await impactAnalysisTab.scrollIntoViewIfNeeded();
await impactAnalysisTab.click();
await waitForAllLoadersToDisappear(page);

await page.getByTestId(
`search-dropdown-${filterTestId}`
).waitFor({ state: 'detached' });
await page.getByTestId('filters-button').click();
await page.getByTestId(`search-dropdown-${filterTestId}`).click();

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -224,6 +224,14 @@ test.describe(
// Click on add rule button
await page.locator('[data-testid="add-rule"]').click();

// add-rule navigates to AddRulePage, a real route change deferred
// under react-router v7's startTransition — without waiting for the
// new page's own marker, addRule()'s fill below can still hit
// [data-testid="rule-name"] on the *previous* page (a read-only span
// for an existing rule, reusing the same testid as the new page's
// input) while the navigation is still pending.
await page.getByTestId('add-rule-title').waitFor({ state: 'visible' });

// Add rule (assuming addRule is a function you have defined elsewhere)
await addRule(page, NEW_RULE_NAME, NEW_RULE_DESCRIPTION, 0);

Expand Down Expand Up @@ -259,6 +267,13 @@ test.describe(
// Click on edit rule button
await page.locator('[data-testid="edit-rule"]').click();

// edit-rule navigates to EditRulePage, a real route change deferred
// under react-router v7's startTransition — wait for the new page's
// own marker before touching [data-testid="rule-name"], which is
// otherwise ambiguous with the read-only rule-name span still on the
// previous page during the pending navigation.
await page.getByTestId('edit-rule-title').waitFor({ state: 'visible' });

// Enter new name
await page.locator('[data-testid="rule-name"]').fill(UPDATED_RULE_NAME);

Expand Down
Loading
Loading