- {withSqlParams.map((param) => (
+ {sqlExpressionItems.map((param) => (
+ key={param.key}>
- {startCase(param.name)}
+ {param.label}
- {hasEditPermission && (
+ {hasEditPermission && param.isEditable && (
{
styleActiveLine: false,
readOnly: true,
}}
- value={param.value ?? ''}
+ value={param.value}
/>
diff --git a/openmetadata-ui/src/main/resources/ui/src/components/DataQuality/IncidentManager/TestCaseResultTab/TestCaseResultTab.test.tsx b/openmetadata-ui/src/main/resources/ui/src/components/DataQuality/IncidentManager/TestCaseResultTab/TestCaseResultTab.test.tsx
index c65288135ef1..d67c778673a0 100644
--- a/openmetadata-ui/src/main/resources/ui/src/components/DataQuality/IncidentManager/TestCaseResultTab/TestCaseResultTab.test.tsx
+++ b/openmetadata-ui/src/main/resources/ui/src/components/DataQuality/IncidentManager/TestCaseResultTab/TestCaseResultTab.test.tsx
@@ -103,11 +103,36 @@ jest.mock('../../../common/EntityDescription/DescriptionV1', () => {
return jest.fn().mockImplementation(() =>
DescriptionV1
);
});
jest.mock('../../../Database/SchemaEditor/SchemaEditor', () => {
- return jest.fn().mockImplementation(() =>
SchemaEditor
);
+ return jest
+ .fn()
+ .mockImplementation(({ value }) => (
+
SchemaEditor: {value}
+ ));
});
jest.mock('../../../Database/Profiler/TestSummary/TestSummary', () => {
return jest.fn().mockImplementation(() =>
TestSummary
);
});
+jest.mock('../../../../utils/EntityVersionUtils', () => ({
+ getComputeRowCountDiffDisplay: jest.fn(),
+ getEntityVersionByField: jest.fn(),
+ getEntityVersionTags: jest.fn().mockReturnValue([]),
+ getParameterValueDiffDisplay: jest.fn(),
+}));
+jest.mock('../../../../utils/TableUtils', () => ({
+ getTagsWithoutTier: jest
+ .fn()
+ .mockImplementation((tags = []) =>
+ tags.filter((tag: TagLabel) => !tag.tagFQN?.startsWith('Tier.'))
+ ),
+ getTierTags: jest
+ .fn()
+ .mockImplementation((tags = []) =>
+ tags.find((tag: TagLabel) => tag.tagFQN?.startsWith('Tier.'))
+ ),
+}));
+jest.mock('../../../../utils/TagsUtils', () => ({
+ createTagObject: jest.fn().mockImplementation((tags) => tags),
+}));
jest.mock('../../AddDataQualityTest/EditTestCaseModal', () => {
return jest.fn().mockImplementation(({ onUpdate, testCase, onCancel }) => (
@@ -127,10 +152,10 @@ const mockGetTestDefinitionById = jest.fn();
jest.mock('../../../../rest/testAPI', () => ({
updateTestCaseById: jest
.fn()
- .mockImplementation(() => mockUpdateTestCaseById()),
+ .mockImplementation((...args) => mockUpdateTestCaseById(...args)),
getTestDefinitionById: jest
.fn()
- .mockImplementation(() => mockGetTestDefinitionById()),
+ .mockImplementation((...args) => mockGetTestDefinitionById(...args)),
TestCaseType: {
all: 'all',
table: 'table',
@@ -163,6 +188,8 @@ describe('TestCaseResultTab', () => {
);
mockUseTestCaseStore.testCase.useDynamicAssertion = undefined;
mockUseTestCaseStore.testCase.computePassedFailedRowCount = undefined;
+ mockGetTestDefinitionById.mockReset();
+ mockUpdateTestCaseById.mockReset();
});
it('Should render component', async () => {
@@ -199,6 +226,67 @@ describe('TestCaseResultTab', () => {
expect(await screen.findByText('EditTestCaseModal')).toBeInTheDocument();
});
+ it('Should fetch test definition with sqlExpression field', async () => {
+ mockGetTestDefinitionById.mockResolvedValue({
+ id: '48063740-ac35-4854-9ab3-b1b542c820fe',
+ name: 'tableColumnCountToEqual',
+ });
+
+ render();
+
+ await screen.findByTestId('test-case-result-tab-container');
+
+ expect(mockGetTestDefinitionById).toHaveBeenCalledWith(
+ '48063740-ac35-4854-9ab3-b1b542c820fe',
+ {
+ fields: ['parameterDefinition', 'sqlExpression'],
+ }
+ );
+ });
+
+ it('Should render SQL expression from test definition for library based test', async () => {
+ mockUseTestCaseStore.testCase.parameterValues = [
+ {
+ name: 'columnCount',
+ value: '10',
+ },
+ ];
+ mockGetTestDefinitionById.mockResolvedValue({
+ id: '48063740-ac35-4854-9ab3-b1b542c820fe',
+ name: 'tableColumnCountToEqual',
+ sqlExpression: 'SELECT COUNT(*) FROM {table}',
+ });
+
+ render();
+
+ expect(await screen.findByText('label.sql-expression')).toBeInTheDocument();
+ expect(
+ await screen.findByText('SchemaEditor: SELECT COUNT(*) FROM {table}')
+ ).toBeInTheDocument();
+ expect(screen.queryByTestId('edit-sql-param-icon')).not.toBeInTheDocument();
+ });
+
+ it('Should prefer test case SQL expression over test definition SQL expression', async () => {
+ mockUseTestCaseStore.testCase.parameterValues = [
+ { name: 'sqlExpression', value: 'select * from dim_address' },
+ ];
+ mockGetTestDefinitionById.mockResolvedValue({
+ id: '48063740-ac35-4854-9ab3-b1b542c820fe',
+ name: 'tableColumnCountToEqual',
+ sqlExpression: 'SELECT COUNT(*) FROM {table}',
+ });
+
+ render();
+
+ expect(
+ await screen.findByText('SchemaEditor: select * from dim_address')
+ ).toBeInTheDocument();
+ expect(
+ screen.queryByText('SchemaEditor: SELECT COUNT(*) FROM {table}')
+ ).not.toBeInTheDocument();
+ expect(screen.getByTestId('edit-sql-param-icon')).toBeInTheDocument();
+ });
+
it('EditTestCaseModal should be removed on cancel click', async () => {
const { container } = render();
From 2966302c9c8c3057c0e6fc94134c66cee73c72a3 Mon Sep 17 00:00:00 2001
From: Piyush Kumar <13725shpiyush@gmail.com>
Date: Sun, 19 Apr 2026 11:49:58 +0530
Subject: [PATCH 2/8] Fix UI tests and license headers
---
.../TestCaseResultPermissions.spec.ts | 19 +++++++++++++++----
.../resources/ui/src/rest/testAPI.test.ts | 13 -------------
2 files changed, 15 insertions(+), 17 deletions(-)
diff --git a/openmetadata-ui/src/main/resources/ui/playwright/e2e/Features/DataQuality/TestCaseResultPermissions.spec.ts b/openmetadata-ui/src/main/resources/ui/playwright/e2e/Features/DataQuality/TestCaseResultPermissions.spec.ts
index 2b59ebda54db..0fb5944fe3c1 100644
--- a/openmetadata-ui/src/main/resources/ui/playwright/e2e/Features/DataQuality/TestCaseResultPermissions.spec.ts
+++ b/openmetadata-ui/src/main/resources/ui/playwright/e2e/Features/DataQuality/TestCaseResultPermissions.spec.ts
@@ -241,7 +241,16 @@ test.describe(
}
);
+ const testDefinitionResponse = viewResultsPage.waitForResponse(
+ (response) =>
+ response.url().includes('/api/v1/dataQuality/testDefinitions/') &&
+ response.url().includes('sqlExpression') &&
+ response.ok()
+ );
+
await visitTestCaseDetailsPage(viewResultsPage);
+ await testDefinitionResponse;
+ await waitForAllLoadersToDisappear(viewResultsPage);
await expect(
viewResultsPage.getByTestId('test-case-result-tab-container')
@@ -249,10 +258,12 @@ test.describe(
expect(decodeURIComponent(testDefinitionRequestUrl)).toContain(
'sqlExpression'
);
- await expect(viewResultsPage.getByText('SQL Expression')).toBeVisible();
- await expect(
- viewResultsPage.getByTestId('sql-expression-container')
- ).toContainText(sqlExpression);
+ const sqlExpressionContainer = viewResultsPage.getByTestId(
+ 'sql-expression-container'
+ );
+ await expect(sqlExpressionContainer).toBeVisible({ timeout: 15000 });
+ await expect(sqlExpressionContainer).toContainText('SQL Expression');
+ await expect(sqlExpressionContainer).toContainText(sqlExpression);
await viewResultsPage.unroute(
'**/api/v1/dataQuality/testDefinitions/*'
diff --git a/openmetadata-ui/src/main/resources/ui/src/rest/testAPI.test.ts b/openmetadata-ui/src/main/resources/ui/src/rest/testAPI.test.ts
index f92df3c7037d..65e73708f682 100644
--- a/openmetadata-ui/src/main/resources/ui/src/rest/testAPI.test.ts
+++ b/openmetadata-ui/src/main/resources/ui/src/rest/testAPI.test.ts
@@ -10,19 +10,6 @@
* See the License for the specific language governing permissions and
* limitations under the License.
*/
-/*
- * Copyright 2026 Collate.
- * Licensed under the Apache License, Version 2.0 (the \"License\");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- * http://www.apache.org/licenses/LICENSE-2.0
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an \"AS IS\" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
import { TestCaseStatus } from '../generated/tests/testCase';
// Mock response data
From df988f97aa15b590b32aca235caf05d9ef496ba3 Mon Sep 17 00:00:00 2001
From: Piyush Kumar <13725shpiyush@gmail.com>
Date: Sun, 19 Apr 2026 14:05:03 +0530
Subject: [PATCH 3/8] fix: resolve playwright timeouts for test cases and data
product assignments
---
.../e2e/Features/DataQuality/TestCaseResultPermissions.spec.ts | 2 +-
.../src/main/resources/ui/playwright/utils/common.ts | 3 ++-
2 files changed, 3 insertions(+), 2 deletions(-)
diff --git a/openmetadata-ui/src/main/resources/ui/playwright/e2e/Features/DataQuality/TestCaseResultPermissions.spec.ts b/openmetadata-ui/src/main/resources/ui/playwright/e2e/Features/DataQuality/TestCaseResultPermissions.spec.ts
index 0fb5944fe3c1..2c41cb11b918 100644
--- a/openmetadata-ui/src/main/resources/ui/playwright/e2e/Features/DataQuality/TestCaseResultPermissions.spec.ts
+++ b/openmetadata-ui/src/main/resources/ui/playwright/e2e/Features/DataQuality/TestCaseResultPermissions.spec.ts
@@ -244,7 +244,7 @@ test.describe(
const testDefinitionResponse = viewResultsPage.waitForResponse(
(response) =>
response.url().includes('/api/v1/dataQuality/testDefinitions/') &&
- response.url().includes('sqlExpression') &&
+ response.request().method() === 'GET' &&
response.ok()
);
diff --git a/openmetadata-ui/src/main/resources/ui/playwright/utils/common.ts b/openmetadata-ui/src/main/resources/ui/playwright/utils/common.ts
index 24f2fd6d365f..76b2e5060d99 100644
--- a/openmetadata-ui/src/main/resources/ui/playwright/utils/common.ts
+++ b/openmetadata-ui/src/main/resources/ui/playwright/utils/common.ts
@@ -511,7 +511,8 @@ export const assignDataProduct = async (
.getByTestId('data-product-dropdown-actions')
.getByTestId('saveAssociatedTag')
.click();
- await patchReq;
+ const patchResponse = await patchReq;
+ expect(patchResponse.status()).toBe(200);
if (pollForInheritance) {
for (const dataProduct of dataProducts) {
From fb3a88a72c95ab9d436dabbd3674c3c5ca2a613b Mon Sep 17 00:00:00 2001
From: Piyush Kumar <13725shpiyush@gmail.com>
Date: Sat, 25 Apr 2026 20:06:43 +0530
Subject: [PATCH 4/8] fix(ui): stabilize DQ SQL Playwright test
Mock the test definition response directly so the limited-permission user does not reuse a failed live response status. Drop the unrelated data-product PATCH status assertion from this PR.
---
.../DataQuality/TestCaseResultPermissions.spec.ts | 9 +++++----
.../src/main/resources/ui/playwright/utils/common.ts | 3 +--
2 files changed, 6 insertions(+), 6 deletions(-)
diff --git a/openmetadata-ui/src/main/resources/ui/playwright/e2e/Features/DataQuality/TestCaseResultPermissions.spec.ts b/openmetadata-ui/src/main/resources/ui/playwright/e2e/Features/DataQuality/TestCaseResultPermissions.spec.ts
index 2c41cb11b918..f6731edac01a 100644
--- a/openmetadata-ui/src/main/resources/ui/playwright/e2e/Features/DataQuality/TestCaseResultPermissions.spec.ts
+++ b/openmetadata-ui/src/main/resources/ui/playwright/e2e/Features/DataQuality/TestCaseResultPermissions.spec.ts
@@ -227,14 +227,15 @@ test.describe(
await viewResultsPage.route(
'**/api/v1/dataQuality/testDefinitions/*',
async (route) => {
- const response = await route.fetch();
- const responseBody = await response.json();
testDefinitionRequestUrl = route.request().url();
await route.fulfill({
- response,
+ status: 200,
+ contentType: 'application/json',
json: {
- ...responseBody,
+ id: 'test-definition-id',
+ name: 'tableRowCountToBeBetween',
+ parameterDefinition: [],
sqlExpression,
},
});
diff --git a/openmetadata-ui/src/main/resources/ui/playwright/utils/common.ts b/openmetadata-ui/src/main/resources/ui/playwright/utils/common.ts
index 76b2e5060d99..24f2fd6d365f 100644
--- a/openmetadata-ui/src/main/resources/ui/playwright/utils/common.ts
+++ b/openmetadata-ui/src/main/resources/ui/playwright/utils/common.ts
@@ -511,8 +511,7 @@ export const assignDataProduct = async (
.getByTestId('data-product-dropdown-actions')
.getByTestId('saveAssociatedTag')
.click();
- const patchResponse = await patchReq;
- expect(patchResponse.status()).toBe(200);
+ await patchReq;
if (pollForInheritance) {
for (const dataProduct of dataProducts) {
From 3a8d0f929b585566fc70f2e7c28bb1d08f0cb977 Mon Sep 17 00:00:00 2001
From: Piyush Kumar <13725shpiyush@gmail.com>
Date: Sun, 26 Apr 2026 21:53:45 +0530
Subject: [PATCH 5/8] fix(ui): stabilize shard 2 Playwright checks
Make table setup retry-safe after partial shard retries, wait for specific Activity API description events, align glossary workflow assertions with the current In Review state, and simplify domain selector search input handling.
---
.../e2e/Features/ActivityAPI.spec.ts | 64 ++++++++++---------
.../Glossary/GlossaryWorkflow.spec.ts | 9 ++-
.../playwright/support/entity/TableClass.ts | 57 ++++++++++++++---
.../resources/ui/playwright/utils/domain.ts | 4 +-
4 files changed, 89 insertions(+), 45 deletions(-)
diff --git a/openmetadata-ui/src/main/resources/ui/playwright/e2e/Features/ActivityAPI.spec.ts b/openmetadata-ui/src/main/resources/ui/playwright/e2e/Features/ActivityAPI.spec.ts
index 36211a5e0f67..397a6b746803 100644
--- a/openmetadata-ui/src/main/resources/ui/playwright/e2e/Features/ActivityAPI.spec.ts
+++ b/openmetadata-ui/src/main/resources/ui/playwright/e2e/Features/ActivityAPI.spec.ts
@@ -47,6 +47,7 @@ type ActivityApiEvent = {
actor?: { displayName?: string; name?: string };
eventType?: string;
summary?: string;
+ [key: string]: unknown;
};
type ActivityApiResponse = {
@@ -70,7 +71,25 @@ const openActivityFeedAndWaitForApi = async (page: Page, entityFqn: string) => {
return (await activityResponse.json()) as ActivityApiResponse;
};
-const waitForActivityEvent = async (entityFqn: string, eventType: string) => {
+const activityEventMatches = (
+ event: ActivityApiEvent,
+ eventType: string,
+ expectedText?: string
+) => {
+ if (event.eventType !== eventType) {
+ return false;
+ }
+
+ return expectedText
+ ? JSON.stringify(event).includes(expectedText)
+ : true;
+};
+
+const waitForActivityEvent = async (
+ entityFqn: string,
+ eventType: string,
+ expectedText?: string
+) => {
const { apiContext, afterAction } = await createAdminApiContext();
const activityUrl = `/api/v1/activity/entity/table/name/${encodeURIComponent(
entityFqn
@@ -90,7 +109,9 @@ const waitForActivityEvent = async (entityFqn: string, eventType: string) => {
const body = (await response.json()) as ActivityApiResponse;
events = body.data ?? [];
- return events.some((event) => event.eventType === eventType);
+ return events.some((event) =>
+ activityEventMatches(event, eventType, expectedText)
+ );
},
{
timeout: 75000,
@@ -100,7 +121,9 @@ const waitForActivityEvent = async (entityFqn: string, eventType: string) => {
)
.toBe(true);
- return events.find((event) => event.eventType === eventType);
+ return events.find((event) =>
+ activityEventMatches(event, eventType, expectedText)
+ );
} finally {
await afterAction();
}
@@ -156,7 +179,7 @@ test.afterAll('Cleanup delete admin user', async ({ browser }) => {
});
test.describe('Activity API - Entity Changes', () => {
- test.describe.configure({ timeout: 120000 });
+ test.describe.configure({ timeout: 180000 });
test.beforeAll('Setup: create entities and users', async ({ browser }) => {
const { apiContext, afterAction } = await performAdminLogin(browser);
@@ -206,7 +229,8 @@ test.describe('Activity API - Entity Changes', () => {
const descriptionEvent = await waitForActivityEvent(
entityFqn,
- 'DescriptionUpdated'
+ 'DescriptionUpdated',
+ newDescription
);
const activityResponse = await openActivityFeedAndWaitForApi(
page,
@@ -216,7 +240,8 @@ test.describe('Activity API - Entity Changes', () => {
'#center-container [data-testid="message-container"]'
);
const renderedDescriptionEvent = activityResponse.data?.find(
- (event) => event.eventType === 'DescriptionUpdated'
+ (event) =>
+ activityEventMatches(event, 'DescriptionUpdated', newDescription)
);
expect(descriptionEvent).toBeDefined();
@@ -326,34 +351,15 @@ test.describe('Activity API - Entity Changes', () => {
await afterAction();
// Wait for activity to be indexed
- await waitForActivityEvent(
+ const activityEvent = await waitForActivityEvent(
testTable.entityResponseData.fullyQualifiedName ?? '',
- 'DescriptionUpdated'
+ 'DescriptionUpdated',
+ uniqueDescription
);
- // Navigate to entity page
- await testTable.visitEntityPage(page);
-
- // Navigate to Activity Feed tab
- await page.getByTestId('activity_feed').click();
- await waitForPageLoaded(page);
-
- // Check if there are any feed items
- const feedContainer = page
- .locator('#center-container [data-testid="message-container"]')
- .filter({
- hasText: uniqueDescription,
- });
-
- await feedContainer.waitFor({ state: 'visible' });
-
- const feedContent = await feedContainer.first().textContent();
-
- // The activity should show the actor's name (admin user who made the change)
- // Activity typically shows username or display name
const actorName = adminUser.responseData.displayName;
- expect(feedContent).toContain(actorName);
+ expect(JSON.stringify(activityEvent)).toContain(actorName);
});
test('Activity event links to the correct entity', async ({ page }) => {
diff --git a/openmetadata-ui/src/main/resources/ui/playwright/e2e/Features/Glossary/GlossaryWorkflow.spec.ts b/openmetadata-ui/src/main/resources/ui/playwright/e2e/Features/Glossary/GlossaryWorkflow.spec.ts
index 72094300d9e4..7678a8ac8cf6 100644
--- a/openmetadata-ui/src/main/resources/ui/playwright/e2e/Features/Glossary/GlossaryWorkflow.spec.ts
+++ b/openmetadata-ui/src/main/resources/ui/playwright/e2e/Features/Glossary/GlossaryWorkflow.spec.ts
@@ -149,7 +149,7 @@ test.describe('Term Status Transitions', () => {
await expect(statusBadge).toHaveText('Approved');
});
- test('should start term as Draft when glossary has reviewers', async ({
+ test('should start term as In Review when glossary has reviewers', async ({
page,
}) => {
await sidebarClick(page, SidebarItem.GLOSSARY);
@@ -175,15 +175,14 @@ test.describe('Term Status Transitions', () => {
// Wait for the table to update
- // Check the term shows in the table with Draft status
+ // Check the term shows in the table with In Review status
const termRow = page.locator(`[data-row-key*="${termName}"]`);
await expect(termRow).toBeVisible();
- // Look for status badge - should be Draft
const statusBadge = termRow.locator('.status-badge');
- await expect(statusBadge).toHaveText('Draft');
+ await expect(statusBadge).toHaveText('In Review');
});
// T-C18: Create term - inherits glossary reviewers
@@ -332,7 +331,7 @@ test('should display correct status badge color and icon', async ({ page }) => {
const statusBadge = termRow.locator('.status-badge');
- await expect(statusBadge).toHaveText('Draft');
+ await expect(statusBadge).toHaveText('In Review');
await expect(statusBadge).toBeVisible();
} finally {
diff --git a/openmetadata-ui/src/main/resources/ui/playwright/support/entity/TableClass.ts b/openmetadata-ui/src/main/resources/ui/playwright/support/entity/TableClass.ts
index a277503b8d15..8e9172237152 100644
--- a/openmetadata-ui/src/main/resources/ui/playwright/support/entity/TableClass.ts
+++ b/openmetadata-ui/src/main/resources/ui/playwright/support/entity/TableClass.ts
@@ -236,7 +236,7 @@ export class TableClass extends EntityClass {
}
);
- let service;
+ let service: ResponseDataType;
if (serviceResponse.status() === 409) {
// Service already exists, fetch it by name
const serviceName = this.service.name;
@@ -260,22 +260,50 @@ export class TableClass extends EntityClass {
const databaseResponse = await apiContext.post('/api/v1/databases', {
data: { ...this.database, service: service.fullyQualifiedName },
});
- if (!databaseResponse.ok()) {
+ let database: ResponseDataWithServiceType;
+ if (databaseResponse.status() === 409) {
+ const databaseResponseByName = await apiContext.get(
+ `/api/v1/databases/name/${encodeURIComponent(
+ `${service.fullyQualifiedName}.${this.database.name}`
+ )}`
+ );
+ if (!databaseResponseByName.ok()) {
+ throw new Error(
+ `TableClass: failed to fetch existing database "${this.database.name}" (${databaseResponseByName.status()}): ${await databaseResponseByName.text()}`
+ );
+ }
+ database = await databaseResponseByName.json();
+ } else if (!databaseResponse.ok()) {
throw new Error(
`TableClass: database create failed (${databaseResponse.status()}): ${await databaseResponse.text()}`
);
+ } else {
+ database = await databaseResponse.json();
}
- const database = await databaseResponse.json();
const schemaResponse = await apiContext.post('/api/v1/databaseSchemas', {
data: { ...this.schema, database: database.fullyQualifiedName },
});
- if (!schemaResponse.ok()) {
+ let schema: ResponseDataWithServiceType;
+ if (schemaResponse.status() === 409) {
+ const schemaResponseByName = await apiContext.get(
+ `/api/v1/databaseSchemas/name/${encodeURIComponent(
+ `${database.fullyQualifiedName}.${this.schema.name}`
+ )}`
+ );
+ if (!schemaResponseByName.ok()) {
+ throw new Error(
+ `TableClass: failed to fetch existing schema "${this.schema.name}" (${schemaResponseByName.status()}): ${await schemaResponseByName.text()}`
+ );
+ }
+ schema = await schemaResponseByName.json();
+ } else if (!schemaResponse.ok()) {
throw new Error(
`TableClass: schema create failed (${schemaResponse.status()}): ${await schemaResponse.text()}`
);
+ } else {
+ schema = await schemaResponse.json();
}
- const schema = await schemaResponse.json();
const entityResponse = await apiContext.post('/api/v1/tables', {
data: {
@@ -283,14 +311,27 @@ export class TableClass extends EntityClass {
databaseSchema: schema.fullyQualifiedName,
},
});
- if (!entityResponse.ok()) {
+ let entity: Table;
+ if (entityResponse.status() === 409) {
+ const entityResponseByName = await apiContext.get(
+ `/api/v1/tables/name/${encodeURIComponent(
+ `${schema.fullyQualifiedName}.${this.entity.name}`
+ )}`
+ );
+ if (!entityResponseByName.ok()) {
+ throw new Error(
+ `TableClass: failed to fetch existing table "${this.entity.name}" (${entityResponseByName.status()}): ${await entityResponseByName.text()}`
+ );
+ }
+ entity = await entityResponseByName.json();
+ } else if (!entityResponse.ok()) {
throw new Error(
`TableClass: table create failed (${entityResponse.status()}): ${await entityResponse.text()}`
);
+ } else {
+ entity = await entityResponse.json();
}
- const entity = await entityResponse.json();
-
this.serviceResponseData = service;
this.databaseResponseData = database;
this.schemaResponseData = schema;
diff --git a/openmetadata-ui/src/main/resources/ui/playwright/utils/domain.ts b/openmetadata-ui/src/main/resources/ui/playwright/utils/domain.ts
index d967e1269b40..862562ba934d 100644
--- a/openmetadata-ui/src/main/resources/ui/playwright/utils/domain.ts
+++ b/openmetadata-ui/src/main/resources/ui/playwright/utils/domain.ts
@@ -1670,9 +1670,7 @@ export const selectDomainFromNavbar = async (
.catch(() => false);
if (isSearchBarVisible) {
- await searchBar.focus();
- await searchBar.press('Control+a');
- await searchBar.pressSequentially(searchTerm);
+ await searchBar.fill(searchTerm);
}
return await domainOption.isVisible().catch(() => false);
From b2a962ffceebae6b5bef413a3347708d4ab1d127 Mon Sep 17 00:00:00 2001
From: Piyush Kumar <13725shpiyush@gmail.com>
Date: Sun, 26 Apr 2026 22:04:04 +0530
Subject: [PATCH 6/8] fix(ci): repair PR check formatting
Replace unsupported GitHub Actions upper() usage in SSO Playwright secrets and apply the UI checkstyle formatter output for changed Playwright files.
---
.github/workflows/playwright-sso-tests.yml | 46 +++++++++----------
.../e2e/Features/ActivityAPI.spec.ts | 9 ++--
.../playwright/support/entity/TableClass.ts | 12 +++--
3 files changed, 35 insertions(+), 32 deletions(-)
diff --git a/.github/workflows/playwright-sso-tests.yml b/.github/workflows/playwright-sso-tests.yml
index 04f627078a3a..33df727305d0 100644
--- a/.github/workflows/playwright-sso-tests.yml
+++ b/.github/workflows/playwright-sso-tests.yml
@@ -34,25 +34,25 @@ on:
- reopened
- ready_for_review
paths:
- - "openmetadata-ui/src/main/resources/ui/src/components/Auth/**"
- - "openmetadata-ui/src/main/resources/ui/src/rest/auth-API*"
- - "openmetadata-ui/src/main/resources/ui/src/utils/AuthProvider*"
- - "openmetadata-ui/src/main/resources/ui/src/utils/TokenServiceUtil*"
- - "openmetadata-ui/src/main/resources/ui/src/utils/UserDataUtils*"
- - "openmetadata-ui/src/main/resources/ui/src/hooks/useSignIn*"
- - "openmetadata-ui/src/main/resources/ui/playwright/e2e/Auth/**"
- - "openmetadata-ui/src/main/resources/ui/playwright/utils/mockOidc*"
- - "openmetadata-ui/src/main/resources/ui/playwright.sso.config.ts"
- - "docker/development/mock-oidc-provider/**"
- - "docker/development/docker-compose.yml"
- - "docker/development/.env.sso-test"
- - ".github/workflows/playwright-sso-tests.yml"
+ - 'openmetadata-ui/src/main/resources/ui/src/components/Auth/**'
+ - 'openmetadata-ui/src/main/resources/ui/src/rest/auth-API*'
+ - 'openmetadata-ui/src/main/resources/ui/src/utils/AuthProvider*'
+ - 'openmetadata-ui/src/main/resources/ui/src/utils/TokenServiceUtil*'
+ - 'openmetadata-ui/src/main/resources/ui/src/utils/UserDataUtils*'
+ - 'openmetadata-ui/src/main/resources/ui/src/hooks/useSignIn*'
+ - 'openmetadata-ui/src/main/resources/ui/playwright/e2e/Auth/**'
+ - 'openmetadata-ui/src/main/resources/ui/playwright/utils/mockOidc*'
+ - 'openmetadata-ui/src/main/resources/ui/playwright.sso.config.ts'
+ - 'docker/development/mock-oidc-provider/**'
+ - 'docker/development/docker-compose.yml'
+ - 'docker/development/.env.sso-test'
+ - '.github/workflows/playwright-sso-tests.yml'
workflow_dispatch:
inputs:
sso_provider:
- description: "SSO Provider to test"
+ description: 'SSO Provider to test'
required: true
- default: "mock-oidc"
+ default: 'mock-oidc'
type: choice
options:
- mock-oidc
@@ -107,9 +107,9 @@ jobs:
uses: jesusvasquez333/verify-pr-label-action@v1.4.0
if: ${{ github.event_name == 'pull_request_target' }}
with:
- github-token: "${{ secrets.GITHUB_TOKEN }}"
- valid-labels: "safe to test"
- pull-request-number: "${{ github.event.pull_request.number }}"
+ github-token: '${{ secrets.GITHUB_TOKEN }}'
+ valid-labels: 'safe to test'
+ pull-request-number: '${{ github.event.pull_request.number }}'
disable-reviews: true
- name: Checkout
@@ -146,9 +146,9 @@ jobs:
- name: Setup Openmetadata Test Environment
uses: ./.github/actions/setup-openmetadata-test-environment
with:
- python-version: "3.10"
- args: "-d postgresql -i false"
- ingestion_dependency: "all"
+ python-version: '3.10'
+ args: '-d postgresql -i false'
+ ingestion_dependency: 'all'
env:
AUTHENTICATION_PROVIDER: ${{ matrix.provider == 'mock-oidc' && 'custom-oidc' || 'basic' }}
CUSTOM_OIDC_AUTHENTICATION_PROVIDER_NAME: ${{ matrix.provider == 'mock-oidc' && 'mock-oidc' || '' }}
@@ -192,8 +192,8 @@ jobs:
run: npx playwright test --config=playwright.sso.config.ts --workers=1
env:
SSO_PROVIDER_TYPE: ${{ matrix.provider }}
- SSO_USERNAME: ${{ secrets[format('{0}_SSO_USERNAME', upper(matrix.provider))] }}
- SSO_PASSWORD: ${{ secrets[format('{0}_SSO_PASSWORD', upper(matrix.provider))] }}
+ SSO_USERNAME: ${{ matrix.provider == 'google' && secrets.GOOGLE_SSO_USERNAME || matrix.provider == 'okta' && secrets.OKTA_SSO_USERNAME || matrix.provider == 'azure' && secrets.AZURE_SSO_USERNAME || matrix.provider == 'auth0' && secrets.AUTH0_SSO_USERNAME || matrix.provider == 'saml' && secrets.SAML_SSO_USERNAME || matrix.provider == 'cognito' && secrets.COGNITO_SSO_USERNAME || '' }}
+ SSO_PASSWORD: ${{ matrix.provider == 'google' && secrets.GOOGLE_SSO_PASSWORD || matrix.provider == 'okta' && secrets.OKTA_SSO_PASSWORD || matrix.provider == 'azure' && secrets.AZURE_SSO_PASSWORD || matrix.provider == 'auth0' && secrets.AUTH0_SSO_PASSWORD || matrix.provider == 'saml' && secrets.SAML_SSO_PASSWORD || matrix.provider == 'cognito' && secrets.COGNITO_SSO_PASSWORD || '' }}
PLAYWRIGHT_IS_OSS: true
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
timeout-minutes: 60
diff --git a/openmetadata-ui/src/main/resources/ui/playwright/e2e/Features/ActivityAPI.spec.ts b/openmetadata-ui/src/main/resources/ui/playwright/e2e/Features/ActivityAPI.spec.ts
index 397a6b746803..7ab5dde0c2b2 100644
--- a/openmetadata-ui/src/main/resources/ui/playwright/e2e/Features/ActivityAPI.spec.ts
+++ b/openmetadata-ui/src/main/resources/ui/playwright/e2e/Features/ActivityAPI.spec.ts
@@ -80,9 +80,7 @@ const activityEventMatches = (
return false;
}
- return expectedText
- ? JSON.stringify(event).includes(expectedText)
- : true;
+ return expectedText ? JSON.stringify(event).includes(expectedText) : true;
};
const waitForActivityEvent = async (
@@ -239,9 +237,8 @@ test.describe('Activity API - Entity Changes', () => {
const feedContainer = page.locator(
'#center-container [data-testid="message-container"]'
);
- const renderedDescriptionEvent = activityResponse.data?.find(
- (event) =>
- activityEventMatches(event, 'DescriptionUpdated', newDescription)
+ const renderedDescriptionEvent = activityResponse.data?.find((event) =>
+ activityEventMatches(event, 'DescriptionUpdated', newDescription)
);
expect(descriptionEvent).toBeDefined();
diff --git a/openmetadata-ui/src/main/resources/ui/playwright/support/entity/TableClass.ts b/openmetadata-ui/src/main/resources/ui/playwright/support/entity/TableClass.ts
index 8e9172237152..24ac4cf42544 100644
--- a/openmetadata-ui/src/main/resources/ui/playwright/support/entity/TableClass.ts
+++ b/openmetadata-ui/src/main/resources/ui/playwright/support/entity/TableClass.ts
@@ -269,7 +269,9 @@ export class TableClass extends EntityClass {
);
if (!databaseResponseByName.ok()) {
throw new Error(
- `TableClass: failed to fetch existing database "${this.database.name}" (${databaseResponseByName.status()}): ${await databaseResponseByName.text()}`
+ `TableClass: failed to fetch existing database "${
+ this.database.name
+ }" (${databaseResponseByName.status()}): ${await databaseResponseByName.text()}`
);
}
database = await databaseResponseByName.json();
@@ -293,7 +295,9 @@ export class TableClass extends EntityClass {
);
if (!schemaResponseByName.ok()) {
throw new Error(
- `TableClass: failed to fetch existing schema "${this.schema.name}" (${schemaResponseByName.status()}): ${await schemaResponseByName.text()}`
+ `TableClass: failed to fetch existing schema "${
+ this.schema.name
+ }" (${schemaResponseByName.status()}): ${await schemaResponseByName.text()}`
);
}
schema = await schemaResponseByName.json();
@@ -320,7 +324,9 @@ export class TableClass extends EntityClass {
);
if (!entityResponseByName.ok()) {
throw new Error(
- `TableClass: failed to fetch existing table "${this.entity.name}" (${entityResponseByName.status()}): ${await entityResponseByName.text()}`
+ `TableClass: failed to fetch existing table "${
+ this.entity.name
+ }" (${entityResponseByName.status()}): ${await entityResponseByName.text()}`
);
}
entity = await entityResponseByName.json();
From 512a3e8f1607fba53c38e88308fe30fa5f9bebe2 Mon Sep 17 00:00:00 2001
From: Piyush Kumar <13725shpiyush@gmail.com>
Date: Mon, 27 Apr 2026 11:10:20 +0530
Subject: [PATCH 7/8] chore: Revert CI and Playwright domain/entity test
stabilization per reviewer feedback
---
.github/workflows/playwright-sso-tests.yml | 46 +++---
.../playwright/support/entity/TableClass.ts | 133 ++--------------
.../resources/ui/playwright/utils/domain.ts | 144 +++---------------
3 files changed, 55 insertions(+), 268 deletions(-)
diff --git a/.github/workflows/playwright-sso-tests.yml b/.github/workflows/playwright-sso-tests.yml
index 33df727305d0..04f627078a3a 100644
--- a/.github/workflows/playwright-sso-tests.yml
+++ b/.github/workflows/playwright-sso-tests.yml
@@ -34,25 +34,25 @@ on:
- reopened
- ready_for_review
paths:
- - 'openmetadata-ui/src/main/resources/ui/src/components/Auth/**'
- - 'openmetadata-ui/src/main/resources/ui/src/rest/auth-API*'
- - 'openmetadata-ui/src/main/resources/ui/src/utils/AuthProvider*'
- - 'openmetadata-ui/src/main/resources/ui/src/utils/TokenServiceUtil*'
- - 'openmetadata-ui/src/main/resources/ui/src/utils/UserDataUtils*'
- - 'openmetadata-ui/src/main/resources/ui/src/hooks/useSignIn*'
- - 'openmetadata-ui/src/main/resources/ui/playwright/e2e/Auth/**'
- - 'openmetadata-ui/src/main/resources/ui/playwright/utils/mockOidc*'
- - 'openmetadata-ui/src/main/resources/ui/playwright.sso.config.ts'
- - 'docker/development/mock-oidc-provider/**'
- - 'docker/development/docker-compose.yml'
- - 'docker/development/.env.sso-test'
- - '.github/workflows/playwright-sso-tests.yml'
+ - "openmetadata-ui/src/main/resources/ui/src/components/Auth/**"
+ - "openmetadata-ui/src/main/resources/ui/src/rest/auth-API*"
+ - "openmetadata-ui/src/main/resources/ui/src/utils/AuthProvider*"
+ - "openmetadata-ui/src/main/resources/ui/src/utils/TokenServiceUtil*"
+ - "openmetadata-ui/src/main/resources/ui/src/utils/UserDataUtils*"
+ - "openmetadata-ui/src/main/resources/ui/src/hooks/useSignIn*"
+ - "openmetadata-ui/src/main/resources/ui/playwright/e2e/Auth/**"
+ - "openmetadata-ui/src/main/resources/ui/playwright/utils/mockOidc*"
+ - "openmetadata-ui/src/main/resources/ui/playwright.sso.config.ts"
+ - "docker/development/mock-oidc-provider/**"
+ - "docker/development/docker-compose.yml"
+ - "docker/development/.env.sso-test"
+ - ".github/workflows/playwright-sso-tests.yml"
workflow_dispatch:
inputs:
sso_provider:
- description: 'SSO Provider to test'
+ description: "SSO Provider to test"
required: true
- default: 'mock-oidc'
+ default: "mock-oidc"
type: choice
options:
- mock-oidc
@@ -107,9 +107,9 @@ jobs:
uses: jesusvasquez333/verify-pr-label-action@v1.4.0
if: ${{ github.event_name == 'pull_request_target' }}
with:
- github-token: '${{ secrets.GITHUB_TOKEN }}'
- valid-labels: 'safe to test'
- pull-request-number: '${{ github.event.pull_request.number }}'
+ github-token: "${{ secrets.GITHUB_TOKEN }}"
+ valid-labels: "safe to test"
+ pull-request-number: "${{ github.event.pull_request.number }}"
disable-reviews: true
- name: Checkout
@@ -146,9 +146,9 @@ jobs:
- name: Setup Openmetadata Test Environment
uses: ./.github/actions/setup-openmetadata-test-environment
with:
- python-version: '3.10'
- args: '-d postgresql -i false'
- ingestion_dependency: 'all'
+ python-version: "3.10"
+ args: "-d postgresql -i false"
+ ingestion_dependency: "all"
env:
AUTHENTICATION_PROVIDER: ${{ matrix.provider == 'mock-oidc' && 'custom-oidc' || 'basic' }}
CUSTOM_OIDC_AUTHENTICATION_PROVIDER_NAME: ${{ matrix.provider == 'mock-oidc' && 'mock-oidc' || '' }}
@@ -192,8 +192,8 @@ jobs:
run: npx playwright test --config=playwright.sso.config.ts --workers=1
env:
SSO_PROVIDER_TYPE: ${{ matrix.provider }}
- SSO_USERNAME: ${{ matrix.provider == 'google' && secrets.GOOGLE_SSO_USERNAME || matrix.provider == 'okta' && secrets.OKTA_SSO_USERNAME || matrix.provider == 'azure' && secrets.AZURE_SSO_USERNAME || matrix.provider == 'auth0' && secrets.AUTH0_SSO_USERNAME || matrix.provider == 'saml' && secrets.SAML_SSO_USERNAME || matrix.provider == 'cognito' && secrets.COGNITO_SSO_USERNAME || '' }}
- SSO_PASSWORD: ${{ matrix.provider == 'google' && secrets.GOOGLE_SSO_PASSWORD || matrix.provider == 'okta' && secrets.OKTA_SSO_PASSWORD || matrix.provider == 'azure' && secrets.AZURE_SSO_PASSWORD || matrix.provider == 'auth0' && secrets.AUTH0_SSO_PASSWORD || matrix.provider == 'saml' && secrets.SAML_SSO_PASSWORD || matrix.provider == 'cognito' && secrets.COGNITO_SSO_PASSWORD || '' }}
+ SSO_USERNAME: ${{ secrets[format('{0}_SSO_USERNAME', upper(matrix.provider))] }}
+ SSO_PASSWORD: ${{ secrets[format('{0}_SSO_PASSWORD', upper(matrix.provider))] }}
PLAYWRIGHT_IS_OSS: true
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
timeout-minutes: 60
diff --git a/openmetadata-ui/src/main/resources/ui/playwright/support/entity/TableClass.ts b/openmetadata-ui/src/main/resources/ui/playwright/support/entity/TableClass.ts
index 24ac4cf42544..27924bfb0bae 100644
--- a/openmetadata-ui/src/main/resources/ui/playwright/support/entity/TableClass.ts
+++ b/openmetadata-ui/src/main/resources/ui/playwright/support/entity/TableClass.ts
@@ -236,7 +236,7 @@ export class TableClass extends EntityClass {
}
);
- let service: ResponseDataType;
+ let service;
if (serviceResponse.status() === 409) {
// Service already exists, fetch it by name
const serviceName = this.service.name;
@@ -260,54 +260,22 @@ export class TableClass extends EntityClass {
const databaseResponse = await apiContext.post('/api/v1/databases', {
data: { ...this.database, service: service.fullyQualifiedName },
});
- let database: ResponseDataWithServiceType;
- if (databaseResponse.status() === 409) {
- const databaseResponseByName = await apiContext.get(
- `/api/v1/databases/name/${encodeURIComponent(
- `${service.fullyQualifiedName}.${this.database.name}`
- )}`
- );
- if (!databaseResponseByName.ok()) {
- throw new Error(
- `TableClass: failed to fetch existing database "${
- this.database.name
- }" (${databaseResponseByName.status()}): ${await databaseResponseByName.text()}`
- );
- }
- database = await databaseResponseByName.json();
- } else if (!databaseResponse.ok()) {
+ if (!databaseResponse.ok()) {
throw new Error(
`TableClass: database create failed (${databaseResponse.status()}): ${await databaseResponse.text()}`
);
- } else {
- database = await databaseResponse.json();
}
+ const database = await databaseResponse.json();
const schemaResponse = await apiContext.post('/api/v1/databaseSchemas', {
data: { ...this.schema, database: database.fullyQualifiedName },
});
- let schema: ResponseDataWithServiceType;
- if (schemaResponse.status() === 409) {
- const schemaResponseByName = await apiContext.get(
- `/api/v1/databaseSchemas/name/${encodeURIComponent(
- `${database.fullyQualifiedName}.${this.schema.name}`
- )}`
- );
- if (!schemaResponseByName.ok()) {
- throw new Error(
- `TableClass: failed to fetch existing schema "${
- this.schema.name
- }" (${schemaResponseByName.status()}): ${await schemaResponseByName.text()}`
- );
- }
- schema = await schemaResponseByName.json();
- } else if (!schemaResponse.ok()) {
+ if (!schemaResponse.ok()) {
throw new Error(
`TableClass: schema create failed (${schemaResponse.status()}): ${await schemaResponse.text()}`
);
- } else {
- schema = await schemaResponse.json();
}
+ const schema = await schemaResponse.json();
const entityResponse = await apiContext.post('/api/v1/tables', {
data: {
@@ -315,29 +283,14 @@ export class TableClass extends EntityClass {
databaseSchema: schema.fullyQualifiedName,
},
});
- let entity: Table;
- if (entityResponse.status() === 409) {
- const entityResponseByName = await apiContext.get(
- `/api/v1/tables/name/${encodeURIComponent(
- `${schema.fullyQualifiedName}.${this.entity.name}`
- )}`
- );
- if (!entityResponseByName.ok()) {
- throw new Error(
- `TableClass: failed to fetch existing table "${
- this.entity.name
- }" (${entityResponseByName.status()}): ${await entityResponseByName.text()}`
- );
- }
- entity = await entityResponseByName.json();
- } else if (!entityResponse.ok()) {
+ if (!entityResponse.ok()) {
throw new Error(
`TableClass: table create failed (${entityResponse.status()}): ${await entityResponse.text()}`
);
- } else {
- entity = await entityResponse.json();
}
+ const entity = await entityResponse.json();
+
this.serviceResponseData = service;
this.databaseResponseData = database;
this.schemaResponseData = schema;
@@ -401,24 +354,6 @@ export class TableClass extends EntityClass {
}
async visitEntityPage(page: Page, searchTerm?: string) {
- if (!this.entityResponseData.fullyQualifiedName) {
- const { EntityDataClass } = await import('./EntityDataClass');
- EntityDataClass.loadResponseData();
- }
-
- if (
- !this.entityResponseData.fullyQualifiedName &&
- this.entityResponseData.id
- ) {
- const response = await page.request.get(
- `/api/v1/tables/${this.entityResponseData.id}`
- );
-
- if (response.ok()) {
- this.entityResponseData = await response.json();
- }
- }
-
const tableFqn = this.entityResponseData.fullyQualifiedName ?? '';
const canUseDirectNavigation =
!searchTerm || (tableFqn.length > 0 && searchTerm === tableFqn);
@@ -427,9 +362,7 @@ export class TableClass extends EntityClass {
const tableResponse = page.waitForResponse(
`/api/v1/tables/name/${encodeURIComponent(tableFqn)}?**`
);
- await page.goto(`/table/${encodeURIComponent(tableFqn)}`, {
- waitUntil: 'domcontentloaded',
- });
+ await page.goto(`/table/${encodeURIComponent(tableFqn)}`);
await tableResponse;
await waitForAllLoadersToDisappear(page);
@@ -583,38 +516,14 @@ export class TableClass extends EntityClass {
patchData: Operation[];
queryParams?: Record;
}) {
- if (
- !this.entityResponseData?.fullyQualifiedName &&
- this.entityResponseData?.id
- ) {
- const tableResponse = await apiContext.get(
- `/api/v1/tables/${this.entityResponseData.id}`
- );
-
- if (tableResponse.ok()) {
- this.entityResponseData = await tableResponse.json();
- }
- }
-
- const tableId = this.entityResponseData?.id;
- const tableFqn = this.entityResponseData?.fullyQualifiedName;
-
- if (!tableId && !tableFqn) {
- throw new Error(
- `TableClass.patch: table id and fullyQualifiedName are missing for table "${
- this.entityResponseData?.name ?? this.entity.name
- }"`
- );
- }
-
+ const fqn = encodeURIComponent(
+ this.entityResponseData?.fullyQualifiedName ?? ''
+ );
const queryString = queryParams
? `?${new URLSearchParams(queryParams).toString()}`
: '';
-
const response = await apiContext.patch(
- tableId
- ? `/api/v1/tables/${tableId}${queryString}`
- : `/api/v1/tables/name/${encodeURIComponent(tableFqn!)}${queryString}`,
+ `/api/v1/tables/name/${fqn}${queryString}`,
{
data: patchData,
headers: {
@@ -673,20 +582,4 @@ export class TableClass extends EntityClass {
entity: this.entityResponseData,
};
}
-
- async setOwner(
- apiContext: APIRequestContext,
- owner: { id: string; type: 'user' | 'team' }
- ) {
- return this.patch({
- apiContext,
- patchData: [
- {
- op: 'add',
- path: '/owners',
- value: [owner],
- },
- ],
- });
- }
}
diff --git a/openmetadata-ui/src/main/resources/ui/playwright/utils/domain.ts b/openmetadata-ui/src/main/resources/ui/playwright/utils/domain.ts
index 862562ba934d..ecec928c3412 100644
--- a/openmetadata-ui/src/main/resources/ui/playwright/utils/domain.ts
+++ b/openmetadata-ui/src/main/resources/ui/playwright/utils/domain.ts
@@ -331,18 +331,17 @@ export const selectDataProduct = async (
.getByPlaceholder('Search');
await waitForAllLoadersToDisappear(page);
- await searchBox.waitFor({ state: 'visible' });
await Promise.all([
- page.waitForResponse('/api/v1/search/query?q=*&index=dataProduct*'),
searchBox.fill(dataProduct.name),
+ page.waitForResponse('/api/v1/search/query?q=*&index=dataProduct*'),
]);
await waitForSearchDebounce(page);
await Promise.all([
- page.waitForResponse('/api/v1/dataProducts/name/*'),
page.getByTestId(dataProduct.name).click(),
+ page.waitForResponse('/api/v1/dataProducts/name/*'),
]);
await waitForAllLoadersToDisappear(page);
@@ -1644,46 +1643,25 @@ export const selectDomainFromNavbar = async (
page: Page,
domain: Domain['responseData']
) => {
- const domainDropdown = page.getByTestId('domain-dropdown');
- const domainTree = page.getByTestId('domain-selectable-tree');
- const searchTerm = domain.displayName ?? domain.name;
- const domainOption = page.getByTestId(`tag-${domain.fullyQualifiedName}`);
-
- const openDropdown = async () => {
- await domainDropdown.click();
- await domainTree.waitFor({ state: 'visible' });
- };
-
- await openDropdown();
-
- const searchBar = domainTree.locator('input[placeholder]').first();
-
- await expect
- .poll(
- async () => {
- if (!(await domainTree.isVisible().catch(() => false))) {
- await openDropdown();
- }
-
- const isSearchBarVisible = await searchBar
- .isVisible()
- .catch(() => false);
-
- if (isSearchBarVisible) {
- await searchBar.fill(searchTerm);
- }
+ await page.getByTestId('domain-dropdown').click();
+ await page.getByTestId('domain-selectable-tree').waitFor({
+ state: 'visible',
+ });
- return await domainOption.isVisible().catch(() => false);
- },
- {
- timeout: 60000,
- intervals: [1000, 2000, 5000],
- message: `Timed out waiting for domain ${searchTerm} to appear in navbar selector`,
- }
- )
- .toBe(true);
+ const searchDomainRes = page.waitForResponse(
+ (response) =>
+ response.url().includes('/api/v1/search/query') &&
+ response.url().includes('index=domain')
+ );
+ await page
+ .getByTestId('domain-selectable-tree')
+ .getByTestId('searchbar')
+ .fill(domain.displayName);
+ await searchDomainRes;
- await domainOption.click();
+ const tagSelector = page.getByTestId(`tag-${domain.fullyQualifiedName}`);
+ await tagSelector.waitFor({ state: 'visible' });
+ await tagSelector.click();
await waitForAllLoadersToDisappear(page);
};
@@ -1846,87 +1824,3 @@ export const openDataProductDrawer = async (page: Page, domain: Domain) => {
await domainOption.waitFor({ state: 'visible', timeout: 5000 });
await domainOption.click();
};
-
-const parseRequestBody = (postData: string | null | undefined) => {
- if (!postData) {
- return {};
- }
- try {
- return JSON.parse(postData) as Record;
- } catch {
- return {};
- }
-};
-
-const matchesDomainBulkCall = (
- url: string,
- method: string,
- action: 'add' | 'remove'
-) =>
- method === 'PUT' &&
- /\/api\/v1\/domains\/[^/]+\/assets\/(add|remove)$/.test(url) &&
- url.endsWith(`/assets/${action}`);
-
-export const waitForDomainAssetsAddDryRun = (page: Page) =>
- page.waitForResponse((response) => {
- const request = response.request();
- if (!matchesDomainBulkCall(response.url(), request.method(), 'add')) {
- return false;
- }
-
- return parseRequestBody(request.postData()).dryRun === true;
- });
-
-export const waitForDomainAssetsAddCommit = (page: Page) =>
- page.waitForResponse((response) => {
- const request = response.request();
- if (!matchesDomainBulkCall(response.url(), request.method(), 'add')) {
- return false;
- }
-
- return parseRequestBody(request.postData()).dryRun !== true;
- });
-
-export const waitForDomainAssetsRemoveDryRun = (page: Page) =>
- page.waitForResponse((response) => {
- const request = response.request();
- if (!matchesDomainBulkCall(response.url(), request.method(), 'remove')) {
- return false;
- }
-
- return parseRequestBody(request.postData()).dryRun === true;
- });
-
-export const waitForDomainAssetsRemoveCommit = (page: Page) =>
- page.waitForResponse((response) => {
- const request = response.request();
- if (!matchesDomainBulkCall(response.url(), request.method(), 'remove')) {
- return false;
- }
-
- return parseRequestBody(request.postData()).dryRun !== true;
- });
-
-export const addAssetToDomainViaApi = async (
- apiContext: APIRequestContext,
- domain: Domain,
- asset: { id: string; type: string }
-) => {
- const fqn =
- domain.responseData?.fullyQualifiedName ?? domain.data.fullyQualifiedName;
- const response = await apiContext.put(
- `/api/v1/domains/${encodeURIComponent(fqn ?? '')}/assets/add`,
- {
- data: { assets: [asset] },
- }
- );
-
- if (!response.ok()) {
- const text = await response.text();
- throw new Error(
- `addAssetToDomainViaApi failed (${response.status()}): ${text}`
- );
- }
-
- return response.json();
-};
From e4679c1ef4cbf94cea2c92a97fbc8f79d1b0e322 Mon Sep 17 00:00:00 2001
From: Piyush Kumar <13725shpiyush@gmail.com>
Date: Mon, 27 Apr 2026 11:23:42 +0530
Subject: [PATCH 8/8] chore: fully revert all unrelated test stabilization
files to match PR base
---
.../e2e/Features/ActivityAPI.spec.ts | 63 ++++----
.../Glossary/GlossaryWorkflow.spec.ts | 9 +-
.../playwright/support/entity/TableClass.ts | 70 ++++++++-
.../resources/ui/playwright/utils/domain.ts | 146 +++++++++++++++---
.../resources/ui/src/rest/testAPI.test.ts | 13 ++
5 files changed, 240 insertions(+), 61 deletions(-)
diff --git a/openmetadata-ui/src/main/resources/ui/playwright/e2e/Features/ActivityAPI.spec.ts b/openmetadata-ui/src/main/resources/ui/playwright/e2e/Features/ActivityAPI.spec.ts
index 7ab5dde0c2b2..36211a5e0f67 100644
--- a/openmetadata-ui/src/main/resources/ui/playwright/e2e/Features/ActivityAPI.spec.ts
+++ b/openmetadata-ui/src/main/resources/ui/playwright/e2e/Features/ActivityAPI.spec.ts
@@ -47,7 +47,6 @@ type ActivityApiEvent = {
actor?: { displayName?: string; name?: string };
eventType?: string;
summary?: string;
- [key: string]: unknown;
};
type ActivityApiResponse = {
@@ -71,23 +70,7 @@ const openActivityFeedAndWaitForApi = async (page: Page, entityFqn: string) => {
return (await activityResponse.json()) as ActivityApiResponse;
};
-const activityEventMatches = (
- event: ActivityApiEvent,
- eventType: string,
- expectedText?: string
-) => {
- if (event.eventType !== eventType) {
- return false;
- }
-
- return expectedText ? JSON.stringify(event).includes(expectedText) : true;
-};
-
-const waitForActivityEvent = async (
- entityFqn: string,
- eventType: string,
- expectedText?: string
-) => {
+const waitForActivityEvent = async (entityFqn: string, eventType: string) => {
const { apiContext, afterAction } = await createAdminApiContext();
const activityUrl = `/api/v1/activity/entity/table/name/${encodeURIComponent(
entityFqn
@@ -107,9 +90,7 @@ const waitForActivityEvent = async (
const body = (await response.json()) as ActivityApiResponse;
events = body.data ?? [];
- return events.some((event) =>
- activityEventMatches(event, eventType, expectedText)
- );
+ return events.some((event) => event.eventType === eventType);
},
{
timeout: 75000,
@@ -119,9 +100,7 @@ const waitForActivityEvent = async (
)
.toBe(true);
- return events.find((event) =>
- activityEventMatches(event, eventType, expectedText)
- );
+ return events.find((event) => event.eventType === eventType);
} finally {
await afterAction();
}
@@ -177,7 +156,7 @@ test.afterAll('Cleanup delete admin user', async ({ browser }) => {
});
test.describe('Activity API - Entity Changes', () => {
- test.describe.configure({ timeout: 180000 });
+ test.describe.configure({ timeout: 120000 });
test.beforeAll('Setup: create entities and users', async ({ browser }) => {
const { apiContext, afterAction } = await performAdminLogin(browser);
@@ -227,8 +206,7 @@ test.describe('Activity API - Entity Changes', () => {
const descriptionEvent = await waitForActivityEvent(
entityFqn,
- 'DescriptionUpdated',
- newDescription
+ 'DescriptionUpdated'
);
const activityResponse = await openActivityFeedAndWaitForApi(
page,
@@ -237,8 +215,8 @@ test.describe('Activity API - Entity Changes', () => {
const feedContainer = page.locator(
'#center-container [data-testid="message-container"]'
);
- const renderedDescriptionEvent = activityResponse.data?.find((event) =>
- activityEventMatches(event, 'DescriptionUpdated', newDescription)
+ const renderedDescriptionEvent = activityResponse.data?.find(
+ (event) => event.eventType === 'DescriptionUpdated'
);
expect(descriptionEvent).toBeDefined();
@@ -348,15 +326,34 @@ test.describe('Activity API - Entity Changes', () => {
await afterAction();
// Wait for activity to be indexed
- const activityEvent = await waitForActivityEvent(
+ await waitForActivityEvent(
testTable.entityResponseData.fullyQualifiedName ?? '',
- 'DescriptionUpdated',
- uniqueDescription
+ 'DescriptionUpdated'
);
+ // Navigate to entity page
+ await testTable.visitEntityPage(page);
+
+ // Navigate to Activity Feed tab
+ await page.getByTestId('activity_feed').click();
+ await waitForPageLoaded(page);
+
+ // Check if there are any feed items
+ const feedContainer = page
+ .locator('#center-container [data-testid="message-container"]')
+ .filter({
+ hasText: uniqueDescription,
+ });
+
+ await feedContainer.waitFor({ state: 'visible' });
+
+ const feedContent = await feedContainer.first().textContent();
+
+ // The activity should show the actor's name (admin user who made the change)
+ // Activity typically shows username or display name
const actorName = adminUser.responseData.displayName;
- expect(JSON.stringify(activityEvent)).toContain(actorName);
+ expect(feedContent).toContain(actorName);
});
test('Activity event links to the correct entity', async ({ page }) => {
diff --git a/openmetadata-ui/src/main/resources/ui/playwright/e2e/Features/Glossary/GlossaryWorkflow.spec.ts b/openmetadata-ui/src/main/resources/ui/playwright/e2e/Features/Glossary/GlossaryWorkflow.spec.ts
index 7678a8ac8cf6..72094300d9e4 100644
--- a/openmetadata-ui/src/main/resources/ui/playwright/e2e/Features/Glossary/GlossaryWorkflow.spec.ts
+++ b/openmetadata-ui/src/main/resources/ui/playwright/e2e/Features/Glossary/GlossaryWorkflow.spec.ts
@@ -149,7 +149,7 @@ test.describe('Term Status Transitions', () => {
await expect(statusBadge).toHaveText('Approved');
});
- test('should start term as In Review when glossary has reviewers', async ({
+ test('should start term as Draft when glossary has reviewers', async ({
page,
}) => {
await sidebarClick(page, SidebarItem.GLOSSARY);
@@ -175,14 +175,15 @@ test.describe('Term Status Transitions', () => {
// Wait for the table to update
- // Check the term shows in the table with In Review status
+ // Check the term shows in the table with Draft status
const termRow = page.locator(`[data-row-key*="${termName}"]`);
await expect(termRow).toBeVisible();
+ // Look for status badge - should be Draft
const statusBadge = termRow.locator('.status-badge');
- await expect(statusBadge).toHaveText('In Review');
+ await expect(statusBadge).toHaveText('Draft');
});
// T-C18: Create term - inherits glossary reviewers
@@ -331,7 +332,7 @@ test('should display correct status badge color and icon', async ({ page }) => {
const statusBadge = termRow.locator('.status-badge');
- await expect(statusBadge).toHaveText('In Review');
+ await expect(statusBadge).toHaveText('Draft');
await expect(statusBadge).toBeVisible();
} finally {
diff --git a/openmetadata-ui/src/main/resources/ui/playwright/support/entity/TableClass.ts b/openmetadata-ui/src/main/resources/ui/playwright/support/entity/TableClass.ts
index 27924bfb0bae..a277503b8d15 100644
--- a/openmetadata-ui/src/main/resources/ui/playwright/support/entity/TableClass.ts
+++ b/openmetadata-ui/src/main/resources/ui/playwright/support/entity/TableClass.ts
@@ -354,6 +354,24 @@ export class TableClass extends EntityClass {
}
async visitEntityPage(page: Page, searchTerm?: string) {
+ if (!this.entityResponseData.fullyQualifiedName) {
+ const { EntityDataClass } = await import('./EntityDataClass');
+ EntityDataClass.loadResponseData();
+ }
+
+ if (
+ !this.entityResponseData.fullyQualifiedName &&
+ this.entityResponseData.id
+ ) {
+ const response = await page.request.get(
+ `/api/v1/tables/${this.entityResponseData.id}`
+ );
+
+ if (response.ok()) {
+ this.entityResponseData = await response.json();
+ }
+ }
+
const tableFqn = this.entityResponseData.fullyQualifiedName ?? '';
const canUseDirectNavigation =
!searchTerm || (tableFqn.length > 0 && searchTerm === tableFqn);
@@ -362,7 +380,9 @@ export class TableClass extends EntityClass {
const tableResponse = page.waitForResponse(
`/api/v1/tables/name/${encodeURIComponent(tableFqn)}?**`
);
- await page.goto(`/table/${encodeURIComponent(tableFqn)}`);
+ await page.goto(`/table/${encodeURIComponent(tableFqn)}`, {
+ waitUntil: 'domcontentloaded',
+ });
await tableResponse;
await waitForAllLoadersToDisappear(page);
@@ -516,14 +536,38 @@ export class TableClass extends EntityClass {
patchData: Operation[];
queryParams?: Record;
}) {
- const fqn = encodeURIComponent(
- this.entityResponseData?.fullyQualifiedName ?? ''
- );
+ if (
+ !this.entityResponseData?.fullyQualifiedName &&
+ this.entityResponseData?.id
+ ) {
+ const tableResponse = await apiContext.get(
+ `/api/v1/tables/${this.entityResponseData.id}`
+ );
+
+ if (tableResponse.ok()) {
+ this.entityResponseData = await tableResponse.json();
+ }
+ }
+
+ const tableId = this.entityResponseData?.id;
+ const tableFqn = this.entityResponseData?.fullyQualifiedName;
+
+ if (!tableId && !tableFqn) {
+ throw new Error(
+ `TableClass.patch: table id and fullyQualifiedName are missing for table "${
+ this.entityResponseData?.name ?? this.entity.name
+ }"`
+ );
+ }
+
const queryString = queryParams
? `?${new URLSearchParams(queryParams).toString()}`
: '';
+
const response = await apiContext.patch(
- `/api/v1/tables/name/${fqn}${queryString}`,
+ tableId
+ ? `/api/v1/tables/${tableId}${queryString}`
+ : `/api/v1/tables/name/${encodeURIComponent(tableFqn!)}${queryString}`,
{
data: patchData,
headers: {
@@ -582,4 +626,20 @@ export class TableClass extends EntityClass {
entity: this.entityResponseData,
};
}
+
+ async setOwner(
+ apiContext: APIRequestContext,
+ owner: { id: string; type: 'user' | 'team' }
+ ) {
+ return this.patch({
+ apiContext,
+ patchData: [
+ {
+ op: 'add',
+ path: '/owners',
+ value: [owner],
+ },
+ ],
+ });
+ }
}
diff --git a/openmetadata-ui/src/main/resources/ui/playwright/utils/domain.ts b/openmetadata-ui/src/main/resources/ui/playwright/utils/domain.ts
index ecec928c3412..d967e1269b40 100644
--- a/openmetadata-ui/src/main/resources/ui/playwright/utils/domain.ts
+++ b/openmetadata-ui/src/main/resources/ui/playwright/utils/domain.ts
@@ -331,17 +331,18 @@ export const selectDataProduct = async (
.getByPlaceholder('Search');
await waitForAllLoadersToDisappear(page);
+ await searchBox.waitFor({ state: 'visible' });
await Promise.all([
- searchBox.fill(dataProduct.name),
page.waitForResponse('/api/v1/search/query?q=*&index=dataProduct*'),
+ searchBox.fill(dataProduct.name),
]);
await waitForSearchDebounce(page);
await Promise.all([
- page.getByTestId(dataProduct.name).click(),
page.waitForResponse('/api/v1/dataProducts/name/*'),
+ page.getByTestId(dataProduct.name).click(),
]);
await waitForAllLoadersToDisappear(page);
@@ -1643,25 +1644,48 @@ export const selectDomainFromNavbar = async (
page: Page,
domain: Domain['responseData']
) => {
- await page.getByTestId('domain-dropdown').click();
- await page.getByTestId('domain-selectable-tree').waitFor({
- state: 'visible',
- });
+ const domainDropdown = page.getByTestId('domain-dropdown');
+ const domainTree = page.getByTestId('domain-selectable-tree');
+ const searchTerm = domain.displayName ?? domain.name;
+ const domainOption = page.getByTestId(`tag-${domain.fullyQualifiedName}`);
+
+ const openDropdown = async () => {
+ await domainDropdown.click();
+ await domainTree.waitFor({ state: 'visible' });
+ };
- const searchDomainRes = page.waitForResponse(
- (response) =>
- response.url().includes('/api/v1/search/query') &&
- response.url().includes('index=domain')
- );
- await page
- .getByTestId('domain-selectable-tree')
- .getByTestId('searchbar')
- .fill(domain.displayName);
- await searchDomainRes;
+ await openDropdown();
+
+ const searchBar = domainTree.locator('input[placeholder]').first();
- const tagSelector = page.getByTestId(`tag-${domain.fullyQualifiedName}`);
- await tagSelector.waitFor({ state: 'visible' });
- await tagSelector.click();
+ await expect
+ .poll(
+ async () => {
+ if (!(await domainTree.isVisible().catch(() => false))) {
+ await openDropdown();
+ }
+
+ const isSearchBarVisible = await searchBar
+ .isVisible()
+ .catch(() => false);
+
+ if (isSearchBarVisible) {
+ await searchBar.focus();
+ await searchBar.press('Control+a');
+ await searchBar.pressSequentially(searchTerm);
+ }
+
+ return await domainOption.isVisible().catch(() => false);
+ },
+ {
+ timeout: 60000,
+ intervals: [1000, 2000, 5000],
+ message: `Timed out waiting for domain ${searchTerm} to appear in navbar selector`,
+ }
+ )
+ .toBe(true);
+
+ await domainOption.click();
await waitForAllLoadersToDisappear(page);
};
@@ -1824,3 +1848,87 @@ export const openDataProductDrawer = async (page: Page, domain: Domain) => {
await domainOption.waitFor({ state: 'visible', timeout: 5000 });
await domainOption.click();
};
+
+const parseRequestBody = (postData: string | null | undefined) => {
+ if (!postData) {
+ return {};
+ }
+ try {
+ return JSON.parse(postData) as Record;
+ } catch {
+ return {};
+ }
+};
+
+const matchesDomainBulkCall = (
+ url: string,
+ method: string,
+ action: 'add' | 'remove'
+) =>
+ method === 'PUT' &&
+ /\/api\/v1\/domains\/[^/]+\/assets\/(add|remove)$/.test(url) &&
+ url.endsWith(`/assets/${action}`);
+
+export const waitForDomainAssetsAddDryRun = (page: Page) =>
+ page.waitForResponse((response) => {
+ const request = response.request();
+ if (!matchesDomainBulkCall(response.url(), request.method(), 'add')) {
+ return false;
+ }
+
+ return parseRequestBody(request.postData()).dryRun === true;
+ });
+
+export const waitForDomainAssetsAddCommit = (page: Page) =>
+ page.waitForResponse((response) => {
+ const request = response.request();
+ if (!matchesDomainBulkCall(response.url(), request.method(), 'add')) {
+ return false;
+ }
+
+ return parseRequestBody(request.postData()).dryRun !== true;
+ });
+
+export const waitForDomainAssetsRemoveDryRun = (page: Page) =>
+ page.waitForResponse((response) => {
+ const request = response.request();
+ if (!matchesDomainBulkCall(response.url(), request.method(), 'remove')) {
+ return false;
+ }
+
+ return parseRequestBody(request.postData()).dryRun === true;
+ });
+
+export const waitForDomainAssetsRemoveCommit = (page: Page) =>
+ page.waitForResponse((response) => {
+ const request = response.request();
+ if (!matchesDomainBulkCall(response.url(), request.method(), 'remove')) {
+ return false;
+ }
+
+ return parseRequestBody(request.postData()).dryRun !== true;
+ });
+
+export const addAssetToDomainViaApi = async (
+ apiContext: APIRequestContext,
+ domain: Domain,
+ asset: { id: string; type: string }
+) => {
+ const fqn =
+ domain.responseData?.fullyQualifiedName ?? domain.data.fullyQualifiedName;
+ const response = await apiContext.put(
+ `/api/v1/domains/${encodeURIComponent(fqn ?? '')}/assets/add`,
+ {
+ data: { assets: [asset] },
+ }
+ );
+
+ if (!response.ok()) {
+ const text = await response.text();
+ throw new Error(
+ `addAssetToDomainViaApi failed (${response.status()}): ${text}`
+ );
+ }
+
+ return response.json();
+};
diff --git a/openmetadata-ui/src/main/resources/ui/src/rest/testAPI.test.ts b/openmetadata-ui/src/main/resources/ui/src/rest/testAPI.test.ts
index 65e73708f682..f92df3c7037d 100644
--- a/openmetadata-ui/src/main/resources/ui/src/rest/testAPI.test.ts
+++ b/openmetadata-ui/src/main/resources/ui/src/rest/testAPI.test.ts
@@ -10,6 +10,19 @@
* See the License for the specific language governing permissions and
* limitations under the License.
*/
+/*
+ * Copyright 2026 Collate.
+ * Licensed under the Apache License, Version 2.0 (the \"License\");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ * http://www.apache.org/licenses/LICENSE-2.0
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an \"AS IS\" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
import { TestCaseStatus } from '../generated/tests/testCase';
// Mock response data