From bae065c67cc496823d60f4334d5a2fe05dd5a4bf Mon Sep 17 00:00:00 2001 From: anuj-kumary Date: Mon, 24 Aug 2026 09:53:07 +0530 Subject: [PATCH 1/6] test(ui): run export/import playwright tests as a dedicated user to prevent background jobs tray from blocking admin UI Co-Authored-By: Claude Sonnet 4.6 --- .../MetricBulkImportExportEdit.spec.ts | 40 +++++++++++++++---- .../e2e/Features/SearchExport.spec.ts | 18 +++++++++ .../e2e/Pages/GlossaryImportExport.spec.ts | 12 ++++-- 3 files changed, 58 insertions(+), 12 deletions(-) diff --git a/openmetadata-ui/src/main/resources/ui/playwright/e2e/Features/MetricBulkImportExportEdit.spec.ts b/openmetadata-ui/src/main/resources/ui/playwright/e2e/Features/MetricBulkImportExportEdit.spec.ts index 60e195acb4ec..4daa7501bd7a 100644 --- a/openmetadata-ui/src/main/resources/ui/playwright/e2e/Features/MetricBulkImportExportEdit.spec.ts +++ b/openmetadata-ui/src/main/resources/ui/playwright/e2e/Features/MetricBulkImportExportEdit.spec.ts @@ -183,6 +183,10 @@ let viewOnlyRole: RolesClass; let metricEditorUser: UserClass; let metricEditorPolicy: PolicyClass; let metricEditorRole: RolesClass; +// Dedicated admin user for export/import tests so that completed background +// jobs accumulate in this user's tray instead of the shared admin session, +// preventing the tray from blocking other admin tests in the same worker. +let metricExportUser: UserClass; let metricTypeId: string | undefined; let metricCustomPropertyName: string; @@ -973,6 +977,9 @@ test.describe( }, ], }); + + metricExportUser = new UserClass(undefined, true); + await metricExportUser.create(apiContext); }); test.afterAll(async () => { @@ -984,6 +991,7 @@ test.describe( metricEditorUser?.delete(apiContext), metricEditorRole?.delete(apiContext), metricEditorPolicy?.delete(apiContext), + metricExportUser?.delete(apiContext), ]); await cleanupFixtures(); await cleanupMetricCustomProperty(); @@ -991,8 +999,11 @@ test.describe( }); test('Admin starts exactly one async export job from the metrics listing', async ({ - page, + browser, }) => { + const page = await browser.newPage(); + await metricExportUser.login(page); + try { await redirectToHomePage(page); await waitForMetricsPage(page); await filterMetrics(page, fixtures.prefix); @@ -1021,22 +1032,26 @@ test.describe( }); await page.locator('.csv-jobs-tray-launcher').click(); await expect(page.locator('.csv-jobs-tray-popover')).toBeVisible(); - // Verify the export job appears in the tray. Parallel workers share the - // admin identity and may have their own active jobs; checking an exact - // count is fragile. Instead, assert that a tray item carrying the export - // label is visible — the exportRequestCount check above already guarantees - // exactly one export request was sent. + // Verify the export job appears in the tray. Each test uses a dedicated + // user session so only this test's own job is visible — checking the + // label is sufficient. await expect( page .locator('.csv-jobs-tray-item') .filter({ hasText: /Exporting Metrics|Exported Metrics/ }) ).toBeVisible(); + } finally { + await page.close(); + } }); test('Admin imports a metric CSV through preview and async apply', async ({ - page, + browser, }) => { test.slow(); + const page = await browser.newPage(); + await metricExportUser.login(page); + try { const importedMetricName = `${fixtures.prefix}_imported`; fixtures.metrics.push({ id: '', @@ -1069,6 +1084,9 @@ test.describe( }); await expectImportedMetricComplexFields(importedMetricName); + } finally { + await page.close(); + } }); test('Admin sees metric CSV validation failures for missing names and invalid references', async ({ @@ -1096,9 +1114,12 @@ test.describe( }); test('Admin imports a CSV update for an existing metric', async ({ - page, + browser, }) => { test.slow(); + const page = await browser.newPage(); + await metricExportUser.login(page); + try { const existingMetricName = fixtures.metrics[1].name; const updatedDisplayName = `${fixtures.prefix} Import Updated`; const csv = createCsv([ @@ -1158,6 +1179,9 @@ test.describe( expect(updatedMetric.extension).toMatchObject({ [metricCustomPropertyName]: 'updated custom value', }); + } finally { + await page.close(); + } }); test('Admin bulk edits filtered metrics from the listing API without export jobs', async ({ diff --git a/openmetadata-ui/src/main/resources/ui/playwright/e2e/Features/SearchExport.spec.ts b/openmetadata-ui/src/main/resources/ui/playwright/e2e/Features/SearchExport.spec.ts index 033c556896fd..d657ad8ff9d9 100644 --- a/openmetadata-ui/src/main/resources/ui/playwright/e2e/Features/SearchExport.spec.ts +++ b/openmetadata-ui/src/main/resources/ui/playwright/e2e/Features/SearchExport.spec.ts @@ -12,6 +12,7 @@ */ import { APIRequestContext, expect, Page } from '@playwright/test'; +import { UserClass } from '../../support/user/UserClass'; import { performAdminLogin } from '../../utils/admin'; import { clickOutside, redirectToExplorePage } from '../../utils/common'; import { waitForAllLoadersToDisappear } from '../../utils/entity'; @@ -24,6 +25,11 @@ import { } from '../../utils/explore'; import { test } from '../fixtures/pages'; +// Dedicated admin user so that completed search-export background jobs +// accumulate in this user's tray instead of the shared admin session, +// preventing the tray from blocking other admin tests in the same worker. +let searchExportUser: UserClass; + const startAsyncExport = async (page: Page) => { const exportAsyncPromise = page.waitForResponse( (response) => @@ -111,12 +117,24 @@ test.describe( headers: { 'Content-Type': 'application/json-patch+json' }, } ); + } + + searchExportUser = new UserClass(undefined, true); + await searchExportUser.create(apiContext); + await afterAction(); + }); + + test.afterAll(async ({ browser }) => { + if (searchExportUser) { + const { apiContext, afterAction } = await performAdminLogin(browser); + await searchExportUser.delete(apiContext); await afterAction(); } }); test.beforeEach(async ({ page }) => { + await searchExportUser.login(page); await redirectToExplorePage(page); }); diff --git a/openmetadata-ui/src/main/resources/ui/playwright/e2e/Pages/GlossaryImportExport.spec.ts b/openmetadata-ui/src/main/resources/ui/playwright/e2e/Pages/GlossaryImportExport.spec.ts index 9ebdeb58a9ad..fa73dbf53216 100644 --- a/openmetadata-ui/src/main/resources/ui/playwright/e2e/Pages/GlossaryImportExport.spec.ts +++ b/openmetadata-ui/src/main/resources/ui/playwright/e2e/Pages/GlossaryImportExport.spec.ts @@ -48,10 +48,11 @@ import { } from '../../utils/importUtils'; import { settingClick, sidebarClick } from '../../utils/sidebar'; -// use the admin user to login -test.use({ - storageState: 'playwright/.auth/admin.json', -}); +// Dedicated admin user for glossary import/export tests. Using a fresh user +// instead of the shared admin.json session prevents completed export/import +// jobs from accumulating in the admin background-jobs tray and blocking other +// admin tests that run in the same CI worker. +const glossaryExportUser = new UserClass(undefined, true); const user1 = new UserClass(); const user2 = new UserClass(); @@ -108,6 +109,7 @@ test.describe('Glossary Bulk Import Export', { tag: '@import-export' }, () => { test.beforeAll('setup pre-test', async () => { const { apiContext, afterAction } = await createAdminApiContext(); + await glossaryExportUser.create(apiContext); await user1.create(apiContext); await user2.create(apiContext); await user3.create(apiContext); @@ -122,6 +124,7 @@ test.describe('Glossary Bulk Import Export', { tag: '@import-export' }, () => { test.afterAll('Cleanup', async () => { const { apiContext, afterAction } = await createAdminApiContext(); + await glossaryExportUser.delete(apiContext); await user1.delete(apiContext); await user2.delete(apiContext); await user3.delete(apiContext); @@ -132,6 +135,7 @@ test.describe('Glossary Bulk Import Export', { tag: '@import-export' }, () => { }); test.beforeEach(async ({ page }) => { + await glossaryExportUser.login(page); await redirectToHomePage(page); }); From e412e44ee9921ea9bd6c139fa1bb1553964ffcc1 Mon Sep 17 00:00:00 2001 From: anuj-kumary Date: Mon, 24 Aug 2026 10:08:55 +0530 Subject: [PATCH 2/6] fix(test): use searchExportUser's API context when polling job completion in SearchExport MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The csvAsyncJobs list endpoint filters results by the requesting user. After switching SearchExport tests to run as searchExportUser, the four completion-polling steps still used performAdminLogin which returned the admin's job list — never finding jobs created by searchExportUser — and timed out. Replace all four performAdminLogin(browser) polling calls with getApiContext(page), which extracts the bearer token from the already- authenticated searchExportUser page and queries jobs as that user. Remove the now-unused browser fixture parameter from the four test signatures. Co-Authored-By: Claude Sonnet 4.6 --- .../e2e/Features/SearchExport.spec.ts | 21 ++++++++----------- 1 file changed, 9 insertions(+), 12 deletions(-) diff --git a/openmetadata-ui/src/main/resources/ui/playwright/e2e/Features/SearchExport.spec.ts b/openmetadata-ui/src/main/resources/ui/playwright/e2e/Features/SearchExport.spec.ts index d657ad8ff9d9..c403b5697ba7 100644 --- a/openmetadata-ui/src/main/resources/ui/playwright/e2e/Features/SearchExport.spec.ts +++ b/openmetadata-ui/src/main/resources/ui/playwright/e2e/Features/SearchExport.spec.ts @@ -14,7 +14,7 @@ import { APIRequestContext, expect, Page } from '@playwright/test'; import { UserClass } from '../../support/user/UserClass'; import { performAdminLogin } from '../../utils/admin'; -import { clickOutside, redirectToExplorePage } from '../../utils/common'; +import { clickOutside, getApiContext, redirectToExplorePage } from '../../utils/common'; import { waitForAllLoadersToDisappear } from '../../utils/entity'; import { clickUpdateButtonIfVisible, @@ -200,7 +200,6 @@ test.describe( test('Search mode visible export downloads CSV with tab-specific row count', async ({ page, - browser, }) => { test.slow(); @@ -227,7 +226,7 @@ test.describe( const jobId = await startAsyncExport(page); await test.step('CSV row count matches the displayed tab count', async () => { - const { apiContext, afterAction } = await performAdminLogin(browser); + const { apiContext, afterAction } = await getApiContext(page); const csvText = await fetchCompletedExportCsv(apiContext, jobId); expect(countCsvResponseRows(csvText)).toBe(expectedCount); @@ -279,7 +278,6 @@ test.describe( test('Filtered search visible export downloads CSV with the filtered record count', async ({ page, - browser, }) => { test.slow(); @@ -348,7 +346,7 @@ test.describe( const jobId = await startAsyncExport(page); await test.step('CSV row count matches the filtered record count', async () => { - const { apiContext, afterAction } = await performAdminLogin(browser); + const { apiContext, afterAction } = await getApiContext(page); const csvText = await fetchCompletedExportCsv(apiContext, jobId); expect(countCsvResponseRows(csvText)).toBe(filteredCount); @@ -359,7 +357,6 @@ test.describe( test('Browse mode visible export downloads CSV with current page row count', async ({ page, - browser, }) => { test.slow(); @@ -397,7 +394,7 @@ test.describe( const jobId = await startAsyncExport(page); await test.step('CSV row count matches the displayed page count', async () => { - const { apiContext, afterAction } = await performAdminLogin(browser); + const { apiContext, afterAction } = await getApiContext(page); const csvText = await fetchCompletedExportCsv(apiContext, jobId); expect(countCsvResponseRows(csvText)).toBe(expectedCount); @@ -447,7 +444,6 @@ test.describe( test('Export queues a background job and downloads from the jobs tray', async ({ page, - browser, }) => { test.slow(); @@ -507,10 +503,11 @@ test.describe( // API first (the same way fetchCompletedExportCsv does), so a stalled job is // named as such and the UI waits that follow are short. // - // performAdminLogin, not page.request: the latter carries the page's cookies - // but not the bearer token these endpoints need, so it returns an error object - // rather than the job array. - const { apiContext, afterAction } = await performAdminLogin(browser); + // getApiContext(page), not page.request: page.request carries cookies but + // not the bearer token the csvAsyncJobs endpoint requires. getApiContext + // extracts the token from the page's storage so the request is authenticated + // as searchExportUser — the same user who created the job. + const { apiContext, afterAction } = await getApiContext(page); await waitForExportJobCompleted(apiContext, jobId); await afterAction(); From 2ea72d59632f0e0adb636cc4c1af266d730a6f41 Mon Sep 17 00:00:00 2001 From: anuj-kumary Date: Mon, 24 Aug 2026 10:23:48 +0530 Subject: [PATCH 3/6] style(ui): fix import ordering and try-block indentation in playwright files MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit organize-imports-cli expands long single-line imports to multi-line and prettier re-indents try-block bodies; commit the post-format state so CI's organize-imports → prettier cycle produces no net diff. Co-Authored-By: Claude Sonnet 4.6 --- .../MetricBulkImportExportEdit.spec.ts | 242 +++++++++--------- .../e2e/Features/SearchExport.spec.ts | 6 +- 2 files changed, 126 insertions(+), 122 deletions(-) diff --git a/openmetadata-ui/src/main/resources/ui/playwright/e2e/Features/MetricBulkImportExportEdit.spec.ts b/openmetadata-ui/src/main/resources/ui/playwright/e2e/Features/MetricBulkImportExportEdit.spec.ts index 4daa7501bd7a..38e268a68541 100644 --- a/openmetadata-ui/src/main/resources/ui/playwright/e2e/Features/MetricBulkImportExportEdit.spec.ts +++ b/openmetadata-ui/src/main/resources/ui/playwright/e2e/Features/MetricBulkImportExportEdit.spec.ts @@ -1004,42 +1004,42 @@ test.describe( const page = await browser.newPage(); await metricExportUser.login(page); try { - await redirectToHomePage(page); - await waitForMetricsPage(page); - await filterMetrics(page, fixtures.prefix); - await openMetricActions(page); - - let exportRequestCount = 0; - page.on('request', (request) => { - if (request.url().includes('/api/v1/metrics/name/*/exportAsync')) { - exportRequestCount += 1; - } - }); - - const exportResponse = page.waitForResponse( - (response) => - response.url().includes('/api/v1/metrics/name/*/exportAsync') && - response.request().method() === 'GET' - ); + await redirectToHomePage(page); + await waitForMetricsPage(page); + await filterMetrics(page, fixtures.prefix); + await openMetricActions(page); + + let exportRequestCount = 0; + page.on('request', (request) => { + if (request.url().includes('/api/v1/metrics/name/*/exportAsync')) { + exportRequestCount += 1; + } + }); - await clickMetricAction(page, 'Export'); - const response = await exportResponse; - expect(response.ok()).toBeTruthy(); - // Verify exactly one export request was fired (no duplicate calls). - await expect.poll(() => exportRequestCount).toBe(1); - await expect(page.locator('.csv-jobs-tray-launcher')).toBeVisible({ - timeout: 30000, - }); - await page.locator('.csv-jobs-tray-launcher').click(); - await expect(page.locator('.csv-jobs-tray-popover')).toBeVisible(); - // Verify the export job appears in the tray. Each test uses a dedicated - // user session so only this test's own job is visible — checking the - // label is sufficient. - await expect( - page - .locator('.csv-jobs-tray-item') - .filter({ hasText: /Exporting Metrics|Exported Metrics/ }) - ).toBeVisible(); + const exportResponse = page.waitForResponse( + (response) => + response.url().includes('/api/v1/metrics/name/*/exportAsync') && + response.request().method() === 'GET' + ); + + await clickMetricAction(page, 'Export'); + const response = await exportResponse; + expect(response.ok()).toBeTruthy(); + // Verify exactly one export request was fired (no duplicate calls). + await expect.poll(() => exportRequestCount).toBe(1); + await expect(page.locator('.csv-jobs-tray-launcher')).toBeVisible({ + timeout: 30000, + }); + await page.locator('.csv-jobs-tray-launcher').click(); + await expect(page.locator('.csv-jobs-tray-popover')).toBeVisible(); + // Verify the export job appears in the tray. Each test uses a dedicated + // user session so only this test's own job is visible — checking the + // label is sufficient. + await expect( + page + .locator('.csv-jobs-tray-item') + .filter({ hasText: /Exporting Metrics|Exported Metrics/ }) + ).toBeVisible(); } finally { await page.close(); } @@ -1052,38 +1052,38 @@ test.describe( const page = await browser.newPage(); await metricExportUser.login(page); try { - const importedMetricName = `${fixtures.prefix}_imported`; - fixtures.metrics.push({ - id: '', - name: importedMetricName, - fullyQualifiedName: importedMetricName, - }); + const importedMetricName = `${fixtures.prefix}_imported`; + fixtures.metrics.push({ + id: '', + name: importedMetricName, + fullyQualifiedName: importedMetricName, + }); - await redirectToHomePage(page); - await waitForMetricsPage(page); - await openMetricActions(page); - await clickMetricAction(page, 'Import'); - await expect(page).toHaveURL(/\/bulk\/import\/metric\/\*/); + await redirectToHomePage(page); + await waitForMetricsPage(page); + await openMetricActions(page); + await clickMetricAction(page, 'Import'); + await expect(page).toHaveURL(/\/bulk\/import\/metric\/\*/); - const csvPath = createMetricCsvFile(importedMetricName); - await uploadMetricCsvAndWaitForPreview(page, csvPath); - await expect( - page.getByRole('gridcell', { exact: true, name: importedMetricName }) - ).toBeVisible(); - await expect( - page.getByRole('button', { name: /Start Import/i }) - ).toBeVisible(); + const csvPath = createMetricCsvFile(importedMetricName); + await uploadMetricCsvAndWaitForPreview(page, csvPath); + await expect( + page.getByRole('gridcell', { exact: true, name: importedMetricName }) + ).toBeVisible(); + await expect( + page.getByRole('button', { name: /Start Import/i }) + ).toBeVisible(); - const applyResponse = waitForMetricImportResponse(page, false); - await page.getByRole('button', { name: /Start Import/i }).click(); - await applyResponse; - await expectMetricImportStatus(page, { - processed: '1', - passed: '1', - failed: '0', - }); + const applyResponse = waitForMetricImportResponse(page, false); + await page.getByRole('button', { name: /Start Import/i }).click(); + await applyResponse; + await expectMetricImportStatus(page, { + processed: '1', + passed: '1', + failed: '0', + }); - await expectImportedMetricComplexFields(importedMetricName); + await expectImportedMetricComplexFields(importedMetricName); } finally { await page.close(); } @@ -1120,65 +1120,65 @@ test.describe( const page = await browser.newPage(); await metricExportUser.login(page); try { - const existingMetricName = fixtures.metrics[1].name; - const updatedDisplayName = `${fixtures.prefix} Import Updated`; - const csv = createCsv([ - [ - existingMetricName, - updatedDisplayName, - 'Metric updated from Playwright CSV', - 'COUNT', - 'COUNT', - '', - 'MONTH', - 'SQL', - 'COUNT(order_id)', - fixtures.metrics[0].fullyQualifiedName, - fixtures.secondTag.fullyQualifiedName, - fixtures.nestedGlossaryTerm.fullyQualifiedName, - 'Tier.Tier3', - `user:${fixtures.owner.name}`, - `team:${fixtures.reviewer.name}`, - fixtures.domain.fullyQualifiedName, - fixtures.dataProduct.fullyQualifiedName, - 'Approved', - `${metricCustomPropertyName}:updated custom value`, - ], - ]); - const csvPath = test - .info() - .outputPath(`${existingMetricName}-update.csv`); - fs.writeFileSync(csvPath, csv); - - await redirectToHomePage(page); - await waitForMetricsPage(page); - await openMetricActions(page); - await clickMetricAction(page, 'Import'); - await expect(page).toHaveURL(/\/bulk\/import\/metric\/\*/); - - await uploadMetricCsvAndWaitForPreview(page, csvPath); - await expect(page.getByText(updatedDisplayName)).toBeVisible(); - - const applyResponse = waitForMetricImportResponse(page, false); - await page.getByRole('button', { name: /Start Import/i }).click(); - const response = await applyResponse; - expect(response.ok()).toBeTruthy(); - await expectMetricImportStatus(page, { - processed: '1', - passed: '1', - failed: '0', - }); + const existingMetricName = fixtures.metrics[1].name; + const updatedDisplayName = `${fixtures.prefix} Import Updated`; + const csv = createCsv([ + [ + existingMetricName, + updatedDisplayName, + 'Metric updated from Playwright CSV', + 'COUNT', + 'COUNT', + '', + 'MONTH', + 'SQL', + 'COUNT(order_id)', + fixtures.metrics[0].fullyQualifiedName, + fixtures.secondTag.fullyQualifiedName, + fixtures.nestedGlossaryTerm.fullyQualifiedName, + 'Tier.Tier3', + `user:${fixtures.owner.name}`, + `team:${fixtures.reviewer.name}`, + fixtures.domain.fullyQualifiedName, + fixtures.dataProduct.fullyQualifiedName, + 'Approved', + `${metricCustomPropertyName}:updated custom value`, + ], + ]); + const csvPath = test + .info() + .outputPath(`${existingMetricName}-update.csv`); + fs.writeFileSync(csvPath, csv); + + await redirectToHomePage(page); + await waitForMetricsPage(page); + await openMetricActions(page); + await clickMetricAction(page, 'Import'); + await expect(page).toHaveURL(/\/bulk\/import\/metric\/\*/); + + await uploadMetricCsvAndWaitForPreview(page, csvPath); + await expect(page.getByText(updatedDisplayName)).toBeVisible(); + + const applyResponse = waitForMetricImportResponse(page, false); + await page.getByRole('button', { name: /Start Import/i }).click(); + const response = await applyResponse; + expect(response.ok()).toBeTruthy(); + await expectMetricImportStatus(page, { + processed: '1', + passed: '1', + failed: '0', + }); - const updatedMetric = await parseResponse( - await apiContext.get( - `/api/v1/metrics/name/${existingMetricName}?fields=extension&include=all` - ), - 'fetch CSV-updated metric' - ); - expect(updatedMetric.displayName).toBe(updatedDisplayName); - expect(updatedMetric.extension).toMatchObject({ - [metricCustomPropertyName]: 'updated custom value', - }); + const updatedMetric = await parseResponse( + await apiContext.get( + `/api/v1/metrics/name/${existingMetricName}?fields=extension&include=all` + ), + 'fetch CSV-updated metric' + ); + expect(updatedMetric.displayName).toBe(updatedDisplayName); + expect(updatedMetric.extension).toMatchObject({ + [metricCustomPropertyName]: 'updated custom value', + }); } finally { await page.close(); } diff --git a/openmetadata-ui/src/main/resources/ui/playwright/e2e/Features/SearchExport.spec.ts b/openmetadata-ui/src/main/resources/ui/playwright/e2e/Features/SearchExport.spec.ts index c403b5697ba7..7201830ac038 100644 --- a/openmetadata-ui/src/main/resources/ui/playwright/e2e/Features/SearchExport.spec.ts +++ b/openmetadata-ui/src/main/resources/ui/playwright/e2e/Features/SearchExport.spec.ts @@ -14,7 +14,11 @@ import { APIRequestContext, expect, Page } from '@playwright/test'; import { UserClass } from '../../support/user/UserClass'; import { performAdminLogin } from '../../utils/admin'; -import { clickOutside, getApiContext, redirectToExplorePage } from '../../utils/common'; +import { + clickOutside, + getApiContext, + redirectToExplorePage, +} from '../../utils/common'; import { waitForAllLoadersToDisappear } from '../../utils/entity'; import { clickUpdateButtonIfVisible, From b2330f43595f9cce7b5a313dbf8e0d13479e2d9d Mon Sep 17 00:00:00 2001 From: anuj-kumary Date: Mon, 24 Aug 2026 12:32:25 +0530 Subject: [PATCH 4/6] fix(test): import test from @playwright/test in SearchExport to get a clean page The fixtures/pages.ts custom page fixture always pre-loads admin.json storage state. searchExportUser.login(page) then can't reach /signin because the JWT in localStorage keeps redirecting back to home, causing every beforeEach to hang until the overall test timeout closes the page with "Target page, context or browser has been closed". Using the base @playwright/test fixture gives a clean, unauthenticated page so login() works correctly. Co-Authored-By: Claude Sonnet 4.6 --- .../resources/ui/playwright/e2e/Features/SearchExport.spec.ts | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/openmetadata-ui/src/main/resources/ui/playwright/e2e/Features/SearchExport.spec.ts b/openmetadata-ui/src/main/resources/ui/playwright/e2e/Features/SearchExport.spec.ts index 7201830ac038..81c682d49473 100644 --- a/openmetadata-ui/src/main/resources/ui/playwright/e2e/Features/SearchExport.spec.ts +++ b/openmetadata-ui/src/main/resources/ui/playwright/e2e/Features/SearchExport.spec.ts @@ -11,7 +11,7 @@ * limitations under the License. */ -import { APIRequestContext, expect, Page } from '@playwright/test'; +import { APIRequestContext, expect, Page, test } from '@playwright/test'; import { UserClass } from '../../support/user/UserClass'; import { performAdminLogin } from '../../utils/admin'; import { @@ -27,7 +27,6 @@ import { getExportModalContent, openExportScopeModal, } from '../../utils/explore'; -import { test } from '../fixtures/pages'; // Dedicated admin user so that completed search-export background jobs // accumulate in this user's tray instead of the shared admin session, From 255caf4d90d8a4a8b494bf0c30dc7f31c55f8199 Mon Sep 17 00:00:00 2001 From: anuj-kumary Date: Mon, 24 Aug 2026 13:23:19 +0530 Subject: [PATCH 5/6] test(ui): assert HTTP 200 status instead of response.ok() for export/import responses Explicit status code check is less ambiguous and avoids flakiness from any future 2xx code the endpoint might return. Co-Authored-By: Claude Sonnet 4.6 --- .../e2e/Features/MetricBulkImportExportEdit.spec.ts | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/openmetadata-ui/src/main/resources/ui/playwright/e2e/Features/MetricBulkImportExportEdit.spec.ts b/openmetadata-ui/src/main/resources/ui/playwright/e2e/Features/MetricBulkImportExportEdit.spec.ts index 38e268a68541..2cb49d922c58 100644 --- a/openmetadata-ui/src/main/resources/ui/playwright/e2e/Features/MetricBulkImportExportEdit.spec.ts +++ b/openmetadata-ui/src/main/resources/ui/playwright/e2e/Features/MetricBulkImportExportEdit.spec.ts @@ -1024,7 +1024,7 @@ test.describe( await clickMetricAction(page, 'Export'); const response = await exportResponse; - expect(response.ok()).toBeTruthy(); + expect(response.status()).toBe(200); // Verify exactly one export request was fired (no duplicate calls). await expect.poll(() => exportRequestCount).toBe(1); await expect(page.locator('.csv-jobs-tray-launcher')).toBeVisible({ @@ -1162,7 +1162,7 @@ test.describe( const applyResponse = waitForMetricImportResponse(page, false); await page.getByRole('button', { name: /Start Import/i }).click(); const response = await applyResponse; - expect(response.ok()).toBeTruthy(); + expect(response.status()).toBe(200); await expectMetricImportStatus(page, { processed: '1', passed: '1', @@ -1222,7 +1222,7 @@ test.describe( const updateResponse = waitForMetricImportResponse(page, false); await page.getByRole('button', { name: 'Update' }).click(); const response = await updateResponse; - expect(response.ok()).toBeTruthy(); + expect(response.status()).toBe(200); await page.waitForURL(/\/metrics/, { timeout: 90000 }); const updatedMetric = await parseResponse( From f943183020c1c5f44b33b6eb91a2128f0f7583a4 Mon Sep 17 00:00:00 2001 From: anuj-kumary Date: Mon, 24 Aug 2026 14:21:22 +0530 Subject: [PATCH 6/6] fix(test): expect 202 for exportAsync response, not 200 The async export endpoint returns 202 Accepted when a job is queued. Co-Authored-By: Claude Sonnet 4.6 --- .../playwright/e2e/Features/MetricBulkImportExportEdit.spec.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/openmetadata-ui/src/main/resources/ui/playwright/e2e/Features/MetricBulkImportExportEdit.spec.ts b/openmetadata-ui/src/main/resources/ui/playwright/e2e/Features/MetricBulkImportExportEdit.spec.ts index 2cb49d922c58..1c6067998a48 100644 --- a/openmetadata-ui/src/main/resources/ui/playwright/e2e/Features/MetricBulkImportExportEdit.spec.ts +++ b/openmetadata-ui/src/main/resources/ui/playwright/e2e/Features/MetricBulkImportExportEdit.spec.ts @@ -1024,7 +1024,7 @@ test.describe( await clickMetricAction(page, 'Export'); const response = await exportResponse; - expect(response.status()).toBe(200); + expect(response.status()).toBe(202); // Verify exactly one export request was fired (no duplicate calls). await expect.poll(() => exportRequestCount).toBe(1); await expect(page.locator('.csv-jobs-tray-launcher')).toBeVisible({