Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
29 commits
Select commit Hold shift + click to select a range
3a931c3
playwright(ci): tighten retries and purge waitForTimeout leaks to cut…
chirag-madlani Jun 24, 2026
b502c23
playwright(ci): tighten annotated waitForTimeout values to cut dead-t…
chirag-madlani Jun 24, 2026
39795f2
playwright(ci): split per-entity CRUD/lineage suites into PR vs stres…
chirag-madlani Jun 24, 2026
f1105e8
playwright(ci): mirror stress shard to mysql-nightly + bump shardTota…
chirag-madlani Jun 24, 2026
1a00978
playwright(ci): disable retries for Login.spec.ts token-expiry tests
chirag-madlani Jun 24, 2026
3569b8f
playwright(ci): revert too-aggressive incidentManager + common wait cuts
chirag-madlani Jun 25, 2026
39961aa
playwright(fix): repair flaky Roles + SearchRBAC tests revealed by re…
chirag-madlani Jun 25, 2026
47dd10d
playwright(fix): cap Domains.Create-DataProducts test at 10 min via e…
chirag-madlani Jun 25, 2026
4a5d1e0
playwright(fix): tighten Domains.Create-DataProducts timeout 10min → …
chirag-madlani Jun 25, 2026
707ce45
Merge remote-tracking branch 'origin/main' into optimize-playwright-e…
chirag-madlani Jun 25, 2026
b6c993d
playwright(fix): revert GlossaryP3Tests special-char wait 300ms → 1000ms
chirag-madlani Jun 25, 2026
b542ea4
Merge remote-tracking branch 'origin/main' into optimize-playwright-e…
chirag-madlani Jun 25, 2026
3d6f667
playwright(fix): ContextCenter vote button class regex matches new Ta…
chirag-madlani Jun 25, 2026
23fe8bd
Merge remote-tracking branch 'origin/main' into optimize-playwright-e…
chirag-madlani Jun 26, 2026
26e9502
Merge branch 'main' into optimize-playwright-e2e-ci
chirag-madlani Jun 26, 2026
1929108
Merge branch 'optimize-playwright-e2e-ci' of https://github.com/open-…
chirag-madlani Jun 26, 2026
322ee6d
playwright(fix): Table.spec pagination test waits for sort + next-pag…
chirag-madlani Jun 26, 2026
860e875
playwright(fix): BulkEditEntity Glossary waits for ES indexing before…
chirag-madlani Jun 26, 2026
c246a6a
playwright(fix): waitForSearchIndexed sweep for bulk-edit / bulk-impo…
chirag-madlani Jun 26, 2026
383c1ad
Merge remote-tracking branch 'origin/main' into optimize-playwright-e…
chirag-madlani Jun 26, 2026
e57023b
playwright(fix): TestCaseImportExportE2eFlow waits for test case inde…
chirag-madlani Jun 26, 2026
d6cd8a2
playwright(fix): BulkImport expectImportRowStatusesToContain waits fo…
chirag-madlani Jun 26, 2026
a4a346e
playwright(fix): restore safety-critical wait timeouts per code review
chirag-madlani Jun 26, 2026
7749b44
playwright(refactor): dedup support/fixtures/userPages into e2e/fixtu…
chirag-madlani Jun 26, 2026
de5b806
playwright(fix): keep eslint-disable adjacent to waitForTimeout in Cu…
chirag-madlani Jun 26, 2026
a23afae
playwright(fix): stabilize ExploreBrowse tree-click + SearchRBAC nega…
chirag-madlani Jun 29, 2026
310b277
Merge remote-tracking branch 'origin/main' into optimize-playwright-e…
chirag-madlani Jun 29, 2026
283bddf
playwright(fix): activity-feed propagation timeout + search-response …
chirag-madlani Jun 29, 2026
4ba7309
playwright(ci): restore retries:2 — flake tail too long to fight per-…
chirag-madlani Jun 29, 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
13 changes: 11 additions & 2 deletions .github/workflows/mysql-nightly-e2e.yml
Original file line number Diff line number Diff line change
Expand Up @@ -42,8 +42,13 @@ jobs:
strategy:
fail-fast: false
matrix:
shardIndex: [1, 2, 3, 4, 5, 6]
shardTotal: [6]
# shard 1: DataAssetRules suite
# shards 2-6: chromium tests sharded 1/5 .. 5/5 (note: --shard=N/5 is
# hardcoded in the run step, shardTotal here is matrix metadata only)
# shard 7: standalone `stress` project (full multi-entity
# CustomProperties + DataAssetLineage coverage deferred from per-PR runs)
shardIndex: [1, 2, 3, 4, 5, 6, 7]
shardTotal: [7]
steps:
- name: Free Disk Space (Ubuntu)
uses: jlumbroso/free-disk-space@main
Expand Down Expand Up @@ -94,6 +99,10 @@ jobs:
--project=DataAssetRulesEnabled \
--project=DataAssetRulesDisabled

elif [ "${{ matrix.shardIndex }}" -eq "7" ]; then
echo "🔹 Running stress suite (full multi-entity coverage)"
npx playwright test --project=stress

else
# Shards 2-6 handle chromium tests (5 shards total)
CHROMIUM_SHARD=$(( ${{ matrix.shardIndex }} - 1 ))
Expand Down
13 changes: 11 additions & 2 deletions .github/workflows/postgresql-nightly-e2e.yml
Original file line number Diff line number Diff line change
Expand Up @@ -42,8 +42,13 @@ jobs:
strategy:
fail-fast: false
matrix:
shardIndex: [1, 2, 3, 4, 5, 6]
shardTotal: [6]
# shard 1: DataAssetRules suite
# shards 2-6: chromium tests sharded 1/5 .. 5/5 (note: --shard=N/5 is
# hardcoded in the run step, shardTotal here is matrix metadata only)
# shard 7: standalone `stress` project (full multi-entity
# CustomProperties + DataAssetLineage coverage deferred from per-PR runs)
shardIndex: [1, 2, 3, 4, 5, 6, 7]
shardTotal: [7]
steps:
- name: Free Disk Space (Ubuntu)
uses: jlumbroso/free-disk-space@main
Expand Down Expand Up @@ -94,6 +99,10 @@ jobs:
--project=DataAssetRulesEnabled \
--project=DataAssetRulesDisabled

elif [ "${{ matrix.shardIndex }}" -eq "7" ]; then
echo "🔹 Running stress suite (full multi-entity coverage)"
npx playwright test --project=stress

else
# Shards 2-5 handle chromium tests (5 shards total)
CHROMIUM_SHARD=$(( ${{ matrix.shardIndex }} - 1 ))
Expand Down
24 changes: 22 additions & 2 deletions openmetadata-ui/src/main/resources/ui/playwright.config.ts
Original file line number Diff line number Diff line change
Expand Up @@ -48,11 +48,19 @@ export default defineConfig({
fullyParallel: true,
/* Fail the build on CI if you accidentally left test.only in the source code. */
forbidOnly: !!process.env.CI,
/* Retry on CI only */
/* Retry on CI only. Kept at 2 — dropping to 1 surfaced a long tail of
* backend-propagation flakes (task feed not refreshing after API task
* create, RBAC index lag, search dropdown not rendering on the first
* query response, etc.) that are not deterministically reproducible
* and each take their own round-trip to investigate. maxFailures:50
* still bails fundamentally broken PRs in minutes. */
retries: process.env.CI ? 2 : 0,
/* Opt out of parallel tests on CI. */
workers: process.env.CI ? 3 : undefined,
maxFailures: 500,
/* Bail early when a PR is fundamentally broken — full-suite has ~4,400
* tests and 50 genuine failures is already far beyond a normal run.
* Healthy runs see <10 failures, so this only kicks in on broken PRs. */
maxFailures: 50,
/* Reporter to use. See https://playwright.dev/docs/test-reporters */
reporter: [
['list'],
Expand Down Expand Up @@ -109,6 +117,7 @@ export default defineConfig({
teardown: 'entity-data-teardown',
testIgnore: [
'**/nightly/**',
'**/stress/**',
'**/Search/**',
'**/Auth/**',
'**/Http2/**',
Expand Down Expand Up @@ -221,6 +230,17 @@ export default defineConfig({
dependencies: ['setup', 'chromium'],
fullyParallel: false,
},
// Stress suite: full multi-entity coverage (CustomProperties, DataAssetLineage)
// that's redundant per-PR. Picks up everything under e2e/stress/**. Run via
// postgresql-nightly-e2e.yml workflow_dispatch — NOT included in PR chromium.
{
name: 'stress',
testMatch: '**/stress/**',
use: { ...devices['Desktop Chrome'] },
dependencies: ['setup', 'entity-data-setup'],
teardown: 'entity-data-teardown',
fullyParallel: true,
},
Comment thread
gitar-bot[bot] marked this conversation as resolved.
Comment thread
gitar-bot[bot] marked this conversation as resolved.
{
name: 'IntakeForm',
testMatch: '**/IntakeForm.spec.ts',
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -129,8 +129,6 @@ test.describe('Activity Stream on Entity Pages', () => {
await activityFeedTab.click();
await waitForPageLoaded(page);

await page.waitForTimeout(2000);

const messageContainers = page.locator('[data-testid="message-container"]');
const count = await messageContainers.count();

Expand Down Expand Up @@ -188,11 +186,12 @@ test.describe('Activity Stream on Entity Pages', () => {
await activityFeedTab.click();
await waitForPageLoaded(page);

await page.waitForTimeout(2000);

const allTabInLeftPanel = page.locator(
'[data-testid="global-setting-left-panel"]'
);
await allTabInLeftPanel
.waitFor({ state: 'visible', timeout: 2000 })
.catch(() => undefined);

if (await allTabInLeftPanel.isVisible()) {
await expect(allTabInLeftPanel).toBeVisible();
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -44,6 +44,7 @@ import {
pressKeyXTimes,
validateImportStatus,
} from '../../utils/importUtils';
import { waitForSearchIndexed } from '../../utils/polling';
import { visitServiceDetailsPage } from '../../utils/service';

interface GlossaryDetails {
Expand Down Expand Up @@ -671,6 +672,18 @@ test.describe('Bulk Edit Entity', () => {
await glossary.create(apiContext);
await glossaryTerm.create(apiContext);

// Wait for the glossary term to be indexed in ES before bulk-edit reads
// the glossary's term list. Otherwise the bulk-edit table comes back
// empty, the test fills row 1 with the term's name, and the system
// creates a new term instead of recognizing the existing one — the
// subsequent status assertion gets "Entity created" instead of
// "Entity updated".
await waitForSearchIndexed(
apiContext,
glossaryTerm.responseData.fullyQualifiedName,
'glossary_term_search_index'
);

await test.step('create custom properties for extension edit', async () => {
customPropertyRecord = await createCustomPropertiesForEntity(
page,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,7 @@ import { GlossaryTerm } from '../../support/glossary/GlossaryTerm';
import { createNewPage, redirectToHomePage } from '../../utils/common';
import { waitForAllLoadersToDisappear } from '../../utils/entity';
import { fillTextInputDetails, pressKeyXTimes } from '../../utils/importUtils';
import { waitForSearchIndexed } from '../../utils/polling';
import { visitServiceDetailsPage } from '../../utils/service';

test.use({ storageState: 'playwright/.auth/admin.json' });
Expand All @@ -39,6 +40,22 @@ test.describe('BulkEditEntity — OperationBadges and Search (all entity types)'
await opGlossary.create(apiContext);
await opGlossaryTerm.create(apiContext);
await opTable.create(apiContext);

// Wait for ES indexing of both the term and the table before any test
// opens the bulk-edit view; the bulk-edit table reads from search and
// otherwise comes back empty under load, racing the test's row count
// assertions.
await waitForSearchIndexed(
apiContext,
opGlossaryTerm.responseData.fullyQualifiedName,
'glossary_term_search_index'
);
await waitForSearchIndexed(
apiContext,
opTable.entityResponseData.fullyQualifiedName ?? '',
'table_search_index'
);

await afterAction();
});

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -49,6 +49,7 @@ import {
startCsvPreviewAndWaitForGrid,
validateImportStatus,
} from '../../utils/importUtils';
import { waitForSearchIndexed } from '../../utils/polling';

// use the admin user to login
test.use({
Expand Down Expand Up @@ -105,6 +106,15 @@ const expectImportRowStatusesToContain = async (
page: Page,
rowStatus: string[]
) => {
// The result grid populates cells asynchronously after Next-click. Without
// first waiting for the row count to match, the toContainText assertion
// can run mid-render against 0 or partial cells, fail under retry too,
// and never recover. Wait for the expected number of detail cells before
// checking text.
await expect(page.locator('.rdg-cell-details')).toHaveCount(
rowStatus.length,
{ timeout: 60_000 }
);
await expect(page.locator('.rdg-cell-details')).toContainText(rowStatus);
};

Expand Down Expand Up @@ -161,6 +171,14 @@ test.describe('Bulk Import Export', () => {
const { apiContext, afterAction } = await getApiContext(page);
await dbService.create(apiContext);

// Bulk-import reads the service's children list from ES; wait for the
// service to be indexed before the test fetches its export/edit grid.
await waitForSearchIndexed(
apiContext,
dbService.entityResponseData.fullyQualifiedName,
'database_service_search_index'
);

await test.step('create custom properties for extension edit', async () => {
customPropertyRecord = await createCustomPropertiesForEntity(
page,
Expand Down Expand Up @@ -613,6 +631,14 @@ test.describe('Bulk Import Export', () => {
const { apiContext, afterAction } = await getApiContext(page);
await dbSchemaEntity.create(apiContext);

// Bulk-import reads the schema's children list from ES; wait for the
// schema to be indexed before the test fetches its export/edit grid.
await waitForSearchIndexed(
apiContext,
dbSchemaEntity.entityResponseData.fullyQualifiedName,
'database_schema_search_index'
);

await test.step('create custom properties for extension edit', async () => {
customPropertyRecord = await createCustomPropertiesForEntity(
page,
Expand Down Expand Up @@ -775,6 +801,14 @@ test.describe('Bulk Import Export', () => {
const { apiContext, afterAction } = await getApiContext(page);
await tableEntity.create(apiContext);

// Bulk-import reads the table's columns from ES; wait for the table
// to be indexed before the test fetches its export/edit grid.
await waitForSearchIndexed(
apiContext,
tableEntity.entityResponseData.fullyQualifiedName ?? '',
'table_search_index'
);

await test.step('should export data table details', async () => {
await tableEntity.visitEntityPage(page);
await performBulkDownload(page, tableEntity.entity.name);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -266,7 +266,7 @@ test.describe('Bulk Import Export with Dot in Service Name', () => {
.waitFor({ state: 'detached', timeout: 60000 });

// eslint-disable-next-line playwright/no-wait-for-timeout -- wait for async import processing to complete
await page.waitForTimeout(2000);
await page.waitForTimeout(500);
});

// Cleanup
Expand Down Expand Up @@ -440,7 +440,7 @@ test.describe('Bulk Import Export with Dot in Service Name', () => {
.waitFor({ state: 'detached', timeout: 60000 });

// eslint-disable-next-line playwright/no-wait-for-timeout -- wait for async import processing to complete
await page.waitForTimeout(2000);
await page.waitForTimeout(500);
});

// Cleanup
Expand Down Expand Up @@ -697,7 +697,7 @@ test.describe('Bulk Import Export with Dot in Service Name', () => {
.waitFor({ state: 'detached', timeout: 60000 });

// eslint-disable-next-line playwright/no-wait-for-timeout -- wait for async import processing to complete
await page.waitForTimeout(2000);
await page.waitForTimeout(500);
});

// Cleanup
Expand Down Expand Up @@ -801,7 +801,7 @@ test.describe('Bulk Import Export with Dot in Service Name', () => {
.waitFor({ state: 'detached', timeout: 60000 });

// eslint-disable-next-line playwright/no-wait-for-timeout -- wait for async import processing to complete
await page.waitForTimeout(2000);
await page.waitForTimeout(500);
});

// Cleanup
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@ import { TableClass } from '../../../support/entity/TableClass';
import { UserClass } from '../../../support/user/UserClass';
import { performAdminLogin } from '../../../utils/admin';
import { redirectToHomePage, uuid } from '../../../utils/common';
import { waitForSearchIndexed } from '../../../utils/polling';
import {
cleanupDownloadedCSV,
performE2EExportImportFlow,
Expand Down Expand Up @@ -67,6 +68,18 @@ test.describe(
const { apiContext, afterAction } = await performAdminLogin(browser);
await table.create(apiContext);
await table.createTestCase(apiContext);

// The export step inside performE2EExportImportFlow reads the table's
// test cases via search. Without waiting for ES indexing here, the
// just-created test case is missing from the exported CSV, so the
// import sees only the 4 added rows (not 5 = 1 existing + 4 added) and
// `processed-row` reports "4" instead of the expected "5".
await waitForSearchIndexed(
apiContext,
table.testCasesResponseData[0]?.fullyQualifiedName ?? '',
'test_case_search_index'
);

await afterAction();
});

Expand Down Expand Up @@ -128,6 +141,16 @@ test.describe(

await table.create(apiContext);
await table.createTestCase(apiContext);

// See the matching note in the Admin describe — without waiting for
// ES indexing the export misses the just-created test case and
// processed-row reports "4" instead of "5".
await waitForSearchIndexed(
apiContext,
table.testCasesResponseData[0]?.fullyQualifiedName ?? '',
'test_case_search_index'
);

await afterAction();
});

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -157,7 +157,7 @@ test.describe('Glossary Status Filter - Large Dataset', () => {
// Ignore timeout
});
// eslint-disable-next-line playwright/no-wait-for-timeout -- filter results need time to render
await page.waitForTimeout(500);
await page.waitForTimeout(200);
};

// Reusable helper to perform search
Expand Down Expand Up @@ -296,7 +296,7 @@ test.describe('Glossary Status Filter - Large Dataset', () => {

await page.locator('.ant-btn-primary', { hasText: 'Save' }).click();
// eslint-disable-next-line playwright/no-wait-for-timeout -- filter state needs time to settle after save
await page.waitForTimeout(1000);
await page.waitForTimeout(300);

const allCount = await getRowCount(page);
expect(allCount).toBeGreaterThanOrEqual(filteredCount);
Expand Down Expand Up @@ -372,7 +372,7 @@ test.describe('Glossary Status Filter - Large Dataset', () => {

await clearSearch(page);
// eslint-disable-next-line playwright/no-wait-for-timeout -- filter results need time to render after clearing search
await page.waitForTimeout(1000);
await page.waitForTimeout(300);

const restoredCount = await getRowCount(page);
expect(restoredCount).toBeGreaterThanOrEqual(searchCount);
Expand Down Expand Up @@ -505,7 +505,7 @@ test.describe('Glossary Status Filter - Large Dataset', () => {
await cancelButton.click();

// eslint-disable-next-line playwright/no-wait-for-timeout -- dropdown dismiss animation needs time to settle
await page.waitForTimeout(500);
await page.waitForTimeout(200);

// Count should remain the same
const afterCancelCount = await getRowCount(page);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -962,7 +962,7 @@ test.describe('Impact Analysis', () => {
await waitForAllLoadersToDisappear(page);

// eslint-disable-next-line playwright/no-wait-for-timeout -- column level lineage data takes time to reflect due to UI processing
await page.waitForTimeout(1000);
await page.waitForTimeout(300);

const searchInput = page.getByTestId('searchbar');
await expect(searchInput).toBeVisible();
Expand All @@ -972,7 +972,7 @@ test.describe('Impact Analysis', () => {

await waitForAllLoadersToDisappear(page);
// eslint-disable-next-line playwright/no-wait-for-timeout -- search filtering needs time to complete
await page.waitForTimeout(500);
await page.waitForTimeout(200);

const rowsWithColumn = page.locator(
`[data-row-key*="${columnName}"], tbody tr:has-text("${columnName}")`
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -13,9 +13,9 @@

import { Browser, expect, Page } from '@playwright/test';
import { EntityClass } from '../../../support/entity/EntityClass';
import { test as baseTest } from '../../../support/fixtures/userPages';
import { UserClass } from '../../../support/user/UserClass';
import { performAdminLogin } from '../../../utils/admin';
import { test as baseTest } from '../../fixtures/pages';

import { SERVICE_ENTITIES } from '../../../constant/service';
import { waitForAllLoadersToDisappear } from '../../../utils/entity';
Expand Down
Loading
Loading