Skip to content

Commit 1b677cc

Browse files
karanh37claudegithub-actions[bot]
authored
fix(playwright): avoid overlay click intercepts in domain and service flows (#33022)
* fix(playwright): click domain name cell in selectDomain, unquarantine tags test The domain listing renders as a table whose row carries data-testid={name}. `selectDomain` clicked the row center, which lands in the glossary-terms cell where each tag is its own link, so the click hit a tag instead of triggering domain navigation and the `/api/v1/domains/name/*` response never fired. Add a `data-testid="entity-name"` to the name cell (renderDomainNameCell) and click that cell in `selectDomain`. Remove the `@quarantine` tag from "Verify domain tags and glossary terms". Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> * chore(playwright): auto-refresh impact-map.generated.json [skip ci] * fix(playwright): apply name-cell click to selectDataProduct, drop QUARANTINE row Address review follow-ups: - selectDataProduct had the identical row-center bug; add data-testid="entity-name" to the data-product name cell (renderDataProductNameCell) and click it instead of the row center. - Remove the now-fixed "Verify domain tags and glossary terms" row from QUARANTINE.md. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> * fix(playwright): drain toast stack before clicking Test Connection Done The toast region renders fixed at bottom-center, the same spot as the Test Connection dialog's Done/OK button. Background "…deleted successfully!" toasts fanned out from parallel workers' cleanup pile up over the button and intercept the click, timing out `testConnection` at serviceIngestion.ts:205. Add `waitForToastStackToClear` (count-based, no strict-mode risk) and wait for the stack to drain before clicking Done/OK. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> * fix(playwright): retry React Aria option clicks that detach mid-click React Aria can close a Select/Dropdown listbox mid-click and detach the option ("element was detached from the DOM" / "<html> intercepts pointer events"), timing out the test. fillDomainForm hit this on the domainType option. Route these trigger→option selections through the existing selectOptionWithRetry helper, which re-checks the trigger's aria-expanded, reopens the popover if closed, and retries the option click. Applied to the 11 sites that follow the Select-toggle pattern (domain form, custom property type/format, schedule interval, lineage export type, ontology relation, test platforms, ROWS strategy, memory type/visibility, glossary term relation). Typeahead/search selections (fill-then-pick) are left untouched — reopening would drop the typed filter. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> --------- Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com> Co-authored-by: github-actions[bot] <github-actions[bot]@users.noreply.github.com>
1 parent 47f36da commit 1b677cc

18 files changed

Lines changed: 138 additions & 66 deletions

openmetadata-ui/src/main/resources/ui/playwright/QUARANTINE.md

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -36,7 +36,6 @@ generated variant rather than per source line.
3636
| `e2e/Pages/Lineage/LineageInteraction.spec.ts` | Verify node panel opens on click | 11/11 | `clickLineageNode``entity-header-display-name` never visible (15s). The topic node is not in the graph the `beforeEach` renders. |
3737
| `e2e/Pages/ExplorePageRightPanel_KnowledgeCenter.spec.ts` | Should remove user owner for knowledgeCenter | 11/11 | `entity-summary-panel-container` → owner chip not found (10s). Regressed around #31853, which removed the welcome-banner dismiss helpers. |
3838
| `e2e/Features/PersonaAIContextRules.spec.ts` | knowledge entity type forces Fully rendered on and disables it | 7/11 | Test timeout. |
39-
| `e2e/Pages/Domains.spec.ts` | Verify domain tags and glossary terms | 6/11 | Fails both attempts more often than it flakes — likely a real defect, not timing. |
4039
| `e2e/Features/Table.spec.ts` | should persist page size | 6/11 | Test timeout after `waitForAllLoadersToDisappear`. |
4140
| `e2e/Pages/TestSuiteDetailsPage.spec.ts` | Add test case modal — filters and select | 3/11 | `waitForResponse` on the test-case search never resolves. |
4241
| `e2e/Features/Glossary/GlossaryHierarchy.spec.ts` | should move term to root of different glossary | 2/11 | Drag-and-drop. |

openmetadata-ui/src/main/resources/ui/playwright/e2e/Features/AppMode/AppModeRouteIsolation.spec.ts

Lines changed: 5 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -27,6 +27,7 @@
2727
*/
2828

2929
import { Page } from '@playwright/test';
30+
import { selectOptionWithRetry } from '../../../utils/common';
3031
import { waitForAllLoadersToDisappear } from '../../../utils/entity';
3132
import { enableAiAppMode } from '../../Utils/appMode';
3233
import { expect, test } from './fixtures';
@@ -77,8 +78,10 @@ test.describe(
7778
response.url().includes('testPlatform=')
7879
);
7980

80-
await page.getByRole('button', { name: 'Test Platforms' }).click();
81-
await page.getByRole('option', { name: 'Deequ', exact: true }).click();
81+
await selectOptionWithRetry(
82+
page.getByRole('button', { name: 'Test Platforms' }),
83+
page.getByRole('option', { name: 'Deequ', exact: true })
84+
);
8285

8386
await testDefinitionsResponse;
8487
await expect(page).toHaveURL(/testPlatforms=Deequ/);

openmetadata-ui/src/main/resources/ui/playwright/e2e/Features/ContextCenterMemories.spec.ts

Lines changed: 9 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,7 @@ import {
1717
createNewPage,
1818
getApiContext,
1919
redirectToHomePage,
20+
selectOptionWithRetry,
2021
uuid,
2122
} from '../../utils/common';
2223
import {
@@ -447,8 +448,10 @@ test.describe(
447448
.fill('This memory has all optional fields populated.');
448449

449450
// Select type: Note
450-
await dialog.getByTestId('memory-type-select').click();
451-
await page.getByRole('option', { name: /note/i }).click();
451+
await selectOptionWithRetry(
452+
dialog.getByTestId('memory-type-select'),
453+
page.getByRole('option', { name: /note/i })
454+
);
452455

453456
const createResPromise = page.waitForResponse(
454457
(res) =>
@@ -1523,8 +1526,10 @@ test.describe(
15231526
);
15241527
await editVisibilityBtn.click();
15251528

1526-
await dialog.getByTestId('memory-visibility-select').click();
1527-
await page.getByRole('option', { name: /private/i }).click();
1529+
await selectOptionWithRetry(
1530+
dialog.getByTestId('memory-visibility-select'),
1531+
page.getByRole('option', { name: /private/i })
1532+
);
15281533

15291534
const updateResPromise = page.waitForResponse(
15301535
new RegExp(`${MEMORIES_API}/${visBadgeMemoryId}`)

openmetadata-ui/src/main/resources/ui/playwright/e2e/Features/DataQuality/TableLevelTests.spec.ts

Lines changed: 5 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,7 @@ import {
1717
createNewPage,
1818
getApiContext,
1919
redirectToHomePage,
20+
selectOptionWithRetry,
2021
} from '../../../utils/common';
2122
import {
2223
clickUpdateButton,
@@ -924,8 +925,10 @@ test.describe(
924925
.getByTestId('code-mirror-container')
925926
.getByRole('textbox')
926927
.fill(' update');
927-
await page.getByRole('button', { name: 'ROWS Strategy' }).click();
928-
await page.getByRole('option', { name: 'COUNT' }).click();
928+
await selectOptionWithRetry(
929+
page.getByRole('button', { name: 'ROWS Strategy' }),
930+
page.getByRole('option', { name: 'COUNT' })
931+
);
929932
await page.locator('[data-id="tableCustomSQLQuery"]').waitFor({
930933
state: 'visible',
931934
});

openmetadata-ui/src/main/resources/ui/playwright/e2e/Features/DataQuality/TestLibrary.spec.ts

Lines changed: 5 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,7 @@ import { DOMAIN_TAGS } from '../../../constant/config';
1515
import {
1616
getApiContext,
1717
redirectToHomePage,
18+
selectOptionWithRetry,
1819
toastNotification,
1920
uuid,
2021
} from '../../../utils/common';
@@ -409,8 +410,10 @@ test.describe(
409410
).toHaveCount(0);
410411

411412
// Add dbt
412-
await page.getByTestId('test-platforms').click();
413-
await page.getByRole('option', { name: 'dbt', exact: true }).click();
413+
await selectOptionWithRetry(
414+
page.getByTestId('test-platforms'),
415+
page.getByRole('option', { name: 'dbt', exact: true })
416+
);
414417

415418
// Close dropdown
416419
await page.keyboard.press('Escape');

openmetadata-ui/src/main/resources/ui/playwright/e2e/Features/LineageExportPNGSnapshot.spec.ts

Lines changed: 5 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,7 @@
1313

1414
import * as fs from 'fs';
1515
import { expect, test } from '../../support/fixtures/base';
16+
import { selectOptionWithRetry } from '../../utils/common';
1617
import { performZoomOut } from '../../utils/lineage';
1718

1819
/**
@@ -82,8 +83,10 @@ test.describe(
8283
.waitFor({ state: 'visible' });
8384

8485
// Select PNG (the modal defaults to CSV for entity lineage)
85-
await page.getByTestId('export-type-select').click();
86-
await page.getByRole('option', { name: 'PNG' }).click();
86+
await selectOptionWithRetry(
87+
page.getByTestId('export-type-select'),
88+
page.getByRole('option', { name: 'PNG' })
89+
);
8790
await expect(page.getByTestId('export-type-select')).toContainText('PNG');
8891

8992
// Trigger download

openmetadata-ui/src/main/resources/ui/playwright/e2e/Features/OntologyQueryRdf.spec.ts

Lines changed: 5 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -20,7 +20,7 @@ import { expect, test } from '../../support/fixtures/base';
2020
import { GlossaryTerm } from '../../support/glossary/GlossaryTerm';
2121
import { OntologyRdfFixture } from '../../support/ontology/OntologyRdfFixture';
2222
import { performAdminLogin } from '../../utils/admin';
23-
import { uuid } from '../../utils/common';
23+
import { selectOptionWithRetry, uuid } from '../../utils/common';
2424
import {
2525
navigateToOntologyStudio,
2626
readGraphEdges,
@@ -221,8 +221,10 @@ test.describe('Ontology scoped query mode', { tag: ['@ontology-rdf'] }, () => {
221221
page.getByTestId('ontology-visual-query-builder')
222222
).toBeVisible();
223223

224-
await page.getByTestId('ontology-builder-relation').click();
225-
await page.getByRole('option', { name: 'Related To' }).click();
224+
await selectOptionWithRetry(
225+
page.getByTestId('ontology-builder-relation'),
226+
page.getByRole('option', { name: 'Related To' })
227+
);
226228
await page.getByTestId('ontology-builder-target').click();
227229
await page
228230
.getByRole('option', { name: target.responseData.displayName })

openmetadata-ui/src/main/resources/ui/playwright/e2e/Pages/Domains.spec.ts

Lines changed: 28 additions & 32 deletions
Original file line numberDiff line numberDiff line change
@@ -1172,42 +1172,38 @@ test.describe('Domains', () => {
11721172
}
11731173
});
11741174

1175-
test(
1176-
'Verify domain tags and glossary terms',
1177-
{ tag: '@quarantine' },
1178-
async ({ page }) => {
1179-
const { afterAction, apiContext } = await getApiContext(page);
1180-
const domain = new Domain();
1181-
try {
1182-
await domain.create(apiContext);
1183-
await page.reload();
1184-
await sidebarClick(page, SidebarItem.DOMAIN);
1185-
await waitForAllLoadersToDisappear(page);
1186-
await selectDomain(page, domain.data);
1187-
await waitForAllLoadersToDisappear(page);
1175+
test('Verify domain tags and glossary terms', async ({ page }) => {
1176+
const { afterAction, apiContext } = await getApiContext(page);
1177+
const domain = new Domain();
1178+
try {
1179+
await domain.create(apiContext);
1180+
await page.reload();
1181+
await sidebarClick(page, SidebarItem.DOMAIN);
1182+
await waitForAllLoadersToDisappear(page);
1183+
await selectDomain(page, domain.data);
1184+
await waitForAllLoadersToDisappear(page);
11881185

1189-
await addTagsAndGlossaryToDomain(page, {
1190-
tagFqn: tag.responseData.fullyQualifiedName,
1191-
glossaryTermFqn: glossaryTerm.responseData.fullyQualifiedName,
1192-
});
1186+
await addTagsAndGlossaryToDomain(page, {
1187+
tagFqn: tag.responseData.fullyQualifiedName,
1188+
glossaryTermFqn: glossaryTerm.responseData.fullyQualifiedName,
1189+
});
11931190

1194-
await redirectToHomePage(page);
1195-
await sidebarClick(page, SidebarItem.DOMAIN);
1196-
await waitForAllLoadersToDisappear(page);
1197-
await selectDomain(page, domain.data);
1191+
await redirectToHomePage(page);
1192+
await sidebarClick(page, SidebarItem.DOMAIN);
1193+
await waitForAllLoadersToDisappear(page);
1194+
await selectDomain(page, domain.data);
11981195

1199-
// Verify tag is visible
1200-
await expect(
1201-
page.locator(
1202-
`[data-testid="tag-${tag.responseData.fullyQualifiedName}"]`
1203-
)
1204-
).toBeVisible();
1205-
} finally {
1206-
await domain.delete(apiContext);
1207-
await afterAction();
1208-
}
1196+
// Verify tag is visible
1197+
await expect(
1198+
page.locator(
1199+
`[data-testid="tag-${tag.responseData.fullyQualifiedName}"]`
1200+
)
1201+
).toBeVisible();
1202+
} finally {
1203+
await domain.delete(apiContext);
1204+
await afterAction();
12091205
}
1210-
);
1206+
});
12111207

12121208
test('Create domain with tags using TagSuggestion', async ({ page }) => {
12131209
const { afterAction, apiContext } = await getApiContext(page);

openmetadata-ui/src/main/resources/ui/playwright/e2e/Pages/GlossaryTermRelationSettings.spec.ts

Lines changed: 5 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,7 @@ import {
1717
getApiContext,
1818
getAuthContext,
1919
getSavedAdminToken,
20+
selectOptionWithRetry,
2021
toastNotification,
2122
uuid,
2223
} from '../../utils/common';
@@ -90,8 +91,10 @@ const fillInput = async (page: Page, testId: string, value: string) => {
9091
};
9192

9293
const selectOption = async (page: Page, testId: string, option: string) => {
93-
await page.getByTestId(testId).click();
94-
await page.getByRole('option', { name: option, exact: true }).click();
94+
await selectOptionWithRetry(
95+
page.getByTestId(testId),
96+
page.getByRole('option', { name: option, exact: true })
97+
);
9598
};
9699

97100
const submitRelationForm = async (

openmetadata-ui/src/main/resources/ui/playwright/utils/common.ts

Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -468,6 +468,26 @@ export const waitForToastToDisappear = async (
468468
.waitFor({ state: 'detached', timeout });
469469
};
470470

471+
/**
472+
* Waits until the toast stack holds no toast, so a click on something beneath it
473+
* cannot be swallowed.
474+
*
475+
* The toast region renders fixed at bottom-center — the same spot as many
476+
* dialogs' action buttons (Test Connection's Done/OK, for one). The backend fans
477+
* async-delete notifications from parallel workers' cleanup out to every socket
478+
* of the logged-in user, so unrelated "…deleted successfully!" toasts can pile up
479+
* over a button and intercept the click. A count assertion is used instead of a
480+
* message-filtered `waitFor` because the intercepting toast can be any of them —
481+
* `toHaveCount(0)` retries until the whole stack has drained and never trips
482+
* strict mode.
483+
*/
484+
export const waitForToastStackToClear = async (
485+
page: Page,
486+
timeout?: number
487+
) => {
488+
await expect(page.getByTestId('alert-bar')).toHaveCount(0, { timeout });
489+
};
490+
471491
/**
472492
* Asserts that the page is showing no error toast, optionally narrowed to the
473493
* ones carrying `message`.

0 commit comments

Comments
 (0)