Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
65 commits
Select commit Hold shift + click to select a range
ebe42dc
fix(playwright): fixed AgentLogStream flakiness
Rohit0301 Aug 19, 2026
526fa79
Fixes #31768: keep live log auto-follow on after a hand-made resume
aniketkatkar97 Aug 19, 2026
c1feeb5
test(playwright): stabilize auth and glossary asset checks
harshach Aug 19, 2026
492b571
test(playwright): isolate shared AUT state
harshach Aug 19, 2026
f56ec36
Merge remote-tracking branch 'origin/pr-31764' into test/aut-31788-5919
harshach Aug 19, 2026
e52791b
Merge remote-tracking branch 'origin/pr-31769' into test/aut-31788-5919
harshach Aug 19, 2026
45fb5f8
test(playwright): apply AUT handover timeout budget
harshach Aug 19, 2026
cfedede
test(playwright): format IPv6 webhook receiver URLs
harshach Aug 19, 2026
c4d198d
test(playwright): format IPv6 webhook receiver URLs
harshach Aug 19, 2026
45b0a6f
test(ui): seed optional following widget
harshach Aug 19, 2026
1394f62
test(ui): seed optional following widget
harshach Aug 19, 2026
91c0009
test(playwright): stabilize async UI state
harshach Aug 20, 2026
f4ece72
test(playwright): stabilize async UI state
harshach Aug 20, 2026
6d57d0f
test(playwright): observe eager glossary asset fetch
harshach Aug 20, 2026
3ffde4c
test(playwright): observe eager glossary asset fetch
harshach Aug 20, 2026
3410834
test(playwright): wait for glossary search index
harshach Aug 20, 2026
a5e0c2d
test(playwright): wait for glossary search index
harshach Aug 20, 2026
e2e1d83
test(playwright): persist following widget before navigation
harshach Aug 20, 2026
cf3a489
test(playwright): persist following widget before navigation
harshach Aug 20, 2026
74a08a6
test(playwright): fix strict AUT failures
harshach Aug 20, 2026
aec4445
test(playwright): fix strict AUT failures
harshach Aug 20, 2026
5bc06b5
fix(data-access): resolve current assignee server-side
harshach Aug 20, 2026
3db3b2b
test(data-access): cover current assignee filter
harshach Aug 20, 2026
f8044a5
Stop IngestionLogStreamLive racing the connector's quiet phase
aniketkatkar97 Aug 20, 2026
aed4224
test(playwright): bound certification popover close race
harshach Aug 20, 2026
2fe87f9
test(playwright): bound certification popover close race
harshach Aug 20, 2026
701ba2d
test(playwright): match traced application contracts
harshach Aug 20, 2026
646e22d
test(playwright): match traced application contracts
harshach Aug 20, 2026
7796fff
test(playwright): wait for routed fallback state
harshach Aug 20, 2026
514f22b
test(playwright): wait for routed fallback state
harshach Aug 20, 2026
cb416b6
test(playwright): wait for certification controls
harshach Aug 20, 2026
13ec310
test(playwright): wait for certification controls
harshach Aug 20, 2026
ef43d64
style(playwright): format search indexing route check
harshach Aug 20, 2026
3b935bc
style(playwright): format search indexing route check
harshach Aug 20, 2026
76cc03b
test(playwright): remove full-lane timing races
harshach Aug 20, 2026
6da9f16
test(playwright): verify exact indexed entity
harshach Aug 20, 2026
59d23f5
test(playwright): remove full-lane timing races
harshach Aug 20, 2026
4a2a438
test(playwright): verify exact indexed entity
harshach Aug 20, 2026
5b3cac3
test(playwright): avoid pipeline status race
harshach Aug 20, 2026
340a1f9
test(playwright): avoid pipeline status race
harshach Aug 20, 2026
4b4fb86
test(playwright): fix focused auth and invalid-route checks
harshach Aug 20, 2026
0212e1c
test(playwright): fix focused auth and invalid-route checks
harshach Aug 20, 2026
fb51632
test(playwright): poll deleted document search exactly
harshach Aug 20, 2026
e0a77d1
test(playwright): poll deleted document search exactly
harshach Aug 20, 2026
f923065
test(playwright): reveal late landing widgets
harshach Aug 20, 2026
13914b1
test(playwright): reveal late landing widgets
harshach Aug 20, 2026
fd71a12
Merge remote-tracking branch 'origin/main' into fix/nightly-e2e-stabi…
harshach Aug 20, 2026
598642d
test(playwright): fix exact search and toast races
harshach Aug 20, 2026
5770cec
test(playwright): isolate column search fixtures
harshach Aug 20, 2026
91ce3d8
Merge remote-tracking branch 'origin/main' into fix/nightly-e2e-stabi…
harshach Aug 20, 2026
3b8846f
Merge complete AUT stability source
harshach Aug 21, 2026
58f9128
Merge remote-tracking branch origin/main into fix/nightly-e2e-stability
harshach Aug 21, 2026
5570d32
test(playwright): preserve data product identity
harshach Aug 21, 2026
6b457aa
test(playwright): serialize shared service versions
harshach Aug 21, 2026
828130b
test(playwright): seed bot auth on stable origin
harshach Aug 21, 2026
7452f93
fix(ui): ignore stale explore facet responses
harshach Aug 21, 2026
f78d607
test(playwright): await exact explore facet searches
harshach Aug 21, 2026
383d8b9
test(playwright): preserve shared governance fixtures
harshach Aug 21, 2026
9f6c250
test(playwright): isolate tenant search settings
harshach Aug 21, 2026
487abc3
fix(ui): preserve relevance for typed mentions
harshach Aug 21, 2026
1fc9b90
test(playwright): stabilize full-suite state
harshach Aug 21, 2026
4bc978a
test(playwright): handle early admin signin redirects
harshach Aug 21, 2026
f8ac430
test(playwright): initialize fresh auth storage safely
harshach Aug 21, 2026
b35160d
test(ui): satisfy quick filter formatting rule
harshach Aug 21, 2026
cb0c38d
test(playwright): surface rejected data product assets
harshach Aug 21, 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
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@
package org.openmetadata.it.tests;

import static org.junit.jupiter.api.Assertions.assertEquals;
import static org.junit.jupiter.api.Assertions.assertFalse;
import static org.junit.jupiter.api.Assertions.assertNotNull;
import static org.junit.jupiter.api.Assertions.assertThrows;
import static org.junit.jupiter.api.Assertions.assertTrue;
Expand All @@ -25,6 +26,7 @@
import org.junit.jupiter.api.extension.ExtendWith;
import org.junit.jupiter.api.parallel.Execution;
import org.junit.jupiter.api.parallel.ExecutionMode;
import org.openmetadata.it.bootstrap.SharedEntities;
import org.openmetadata.it.factories.DatabaseSchemaTestFactory;
import org.openmetadata.it.factories.TableTestFactory;
import org.openmetadata.it.util.SdkClients;
Expand Down Expand Up @@ -140,14 +142,46 @@ private static Task createDataAccessRequest(
String entityType,
String entityFqn,
Map<String, Object> payload) {
CreateTask request =
new CreateTask()
.withName(ns.prefix("dar_" + entityType + "_" + UUID.randomUUID()))
.withCategory(TaskCategory.DataAccess)
.withType(TaskEntityType.DataAccessRequest)
.withAbout(entityLink(entityType, entityFqn))
.withPayload(payload);
return client.tasks().create(request);
return client.tasks().create(dataAccessRequest(ns, entityType, entityFqn, payload));
}

private static CreateTask dataAccessRequest(
TestNamespace ns, String entityType, String entityFqn, Map<String, Object> payload) {
return new CreateTask()
.withName(ns.prefix("dar_" + entityType + "_" + UUID.randomUUID()))
.withCategory(TaskCategory.DataAccess)
.withType(TaskEntityType.DataAccessRequest)
.withAbout(entityLink(entityType, entityFqn))
.withPayload(payload);
}

@Test
void testDarAssignedToCurrentUser_filtersByAuthenticatedPrincipal(TestNamespace ns) {
Table table = createTableOnSnowflakeService(ns, baseSnowflakeConnection());
String tableFqn = table.getFullyQualifiedName();
SharedEntities shared = SharedEntities.get();

Task user1Task =
SdkClients.adminClient()
.tasks()
.create(
dataAccessRequest(ns, "table", tableFqn, dataAccessPayload("FullAccess"))
.withAssignees(List.of(shared.USER1.getFullyQualifiedName())));
Task user2Task =
SdkClients.user3Client()
.tasks()
.create(
dataAccessRequest(ns, "table", tableFqn, dataAccessPayload("FullAccess"))
.withAssignees(List.of(shared.USER2.getFullyQualifiedName())));

var assignedToUser1 =
SdkClients.user1Client()
.tasks()
.listDataAccessRequests(
Map.of("dataset", tableFqn, "assignedToMe", "true", "limit", "50"));
List<UUID> taskIds = assignedToUser1.getData().stream().map(Task::getId).toList();
assertTrue(taskIds.contains(user1Task.getId()));
assertFalse(taskIds.contains(user2Task.getId()));
}

@Test
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -444,6 +444,13 @@ public ResultList<Task> listDataAccessRequests(
@Parameter(description = "Filter by assignee user/team id (single UUID).")
@QueryParam("assigneeId")
UUID assigneeId,
@Parameter(
description =
"Filter to tasks assigned to the authenticated user or any of their teams. "
+ "When true, this takes precedence over assignee and assigneeId.")
@QueryParam("assignedToMe")
@DefaultValue("false")
boolean assignedToMe,
@Parameter(description = "Filter by domain FQN") @QueryParam("domain") String domain,
@Parameter(
description =
Expand Down Expand Up @@ -507,12 +514,7 @@ public ResultList<Task> listDataAccessRequests(
validateCsvAgainstAccessType(accessType);
filter.addQueryParam("accessType", accessType);
}
if (!nullOrEmpty(assignee)) {
filter.addQueryParam("assignee", assignee);
}
if (assigneeId != null) {
filter.addQueryParam("assigneeId", assigneeId.toString());
}
addDataAccessRequestAssigneeFilter(filter, securityContext, assignee, assigneeId, assignedToMe);
if (!nullOrEmpty(q)) {
filter.addQueryParam("darSearch", q);
}
Expand Down Expand Up @@ -1375,6 +1377,25 @@ private void addCurrentUserVisibleFilters(
filter.addQueryParam("visibleOwnedByIds", getCurrentUserOwnedIds(uriInfo, securityContext));
}

private void addDataAccessRequestAssigneeFilter(
ListFilter filter,
SecurityContext securityContext,
String assignee,
UUID assigneeId,
boolean assignedToMe) {
if (assignedToMe) {
// Resolve memberships to immutable IDs so clients cannot omit teams or misquote FQNs.
filter.addQueryParam("assigneeIds", getCurrentUserAssigneeIds(securityContext));
} else {
if (!nullOrEmpty(assignee)) {
filter.addQueryParam("assignee", assignee);
}
if (assigneeId != null) {
filter.addQueryParam("assigneeId", assigneeId.toString());
}
}
}

private String getCurrentUserAssigneeIds(SecurityContext securityContext) {
String userName = securityContext.getUserPrincipal().getName();
User user = Entity.getEntityByName(Entity.USER, userName, "teams", Include.NON_DELETED);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -334,10 +334,19 @@ test.describe('Agent log stream handover to the paginated endpoint', () => {
test('Scrolling a live log pauses auto-follow and the toolbar toggle resumes it', async ({
page,
}) => {
// Every step below waits for the stream to append again, and a reconnect can
// take seconds on a loaded runner. The default budget is one such wait, not
// the four this test needs, so it timed out on retry rather than failing.
test.slow();
// This scenario's declared waits cannot fit the 60s project default: the
// steps below carry expect.poll budgets of 60s (wrap relayout) + 30s
// (gestureless drag) + 60s (append while paused) + 60s (append while
// followed), plus ~15s attribute expects between them. Those budgets are
// deliberate — the mock closes every connection, so appends arrive on the
// client's reconnect-backoff cadence, which stretches under shard load.
// With the default ceiling the test killed itself mid-poll while the
// stream was legitimately still backing off (merge-queue runs
// 32238830063, 32244090565, 32248621065, 32249698790: "Test timeout of
// 60000ms exceeded" with the line count about to grow). Budget = sum of
// declared polls + interaction slack; a genuine assertion failure still
// fails fast via the per-expect 15s timeouts.
test.setTimeout(240_000);

await openAgentLogs(page, {
terminal: false,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -136,6 +136,11 @@ function getColumnRowCheckbox(page: Page, rowId: string) {
}

test.describe('Column Bulk Operations - Tags & Glossary Select in Drawer', () => {
// This suite deliberately shares one table and glossary term from beforeAll.
// Opt out of fully-parallel execution so Playwright does not run beforeAll
// once per test and race two creates for the same glossary name.
test.describe.configure({ mode: 'default' });

const CLASSIFICATION_TAG_FQN = 'PII.Sensitive';
const table = new TableClass();
const glossaryTerm = new GlossaryTerm();
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -323,9 +323,9 @@ test.describe('Context Center - Archive Page', () => {
});
});

// ─── Suite: Folder delete — file absent from search and archive ────────────────
// ─── Suite: Folder delete — file absent from search, present in archive ───────

test.describe('Context Center - Folder Delete: file absent from search and archive', () => {
test.describe('Context Center - Folder Delete: file absent from search and present in archive', () => {
let folder: ContextCenterFolder;
let documentId = '';
const folderName = `folder-delete-test-${uuid()}`;
Expand Down Expand Up @@ -353,7 +353,7 @@ test.describe('Context Center - Folder Delete: file absent from search and archi
await redirectToHomePage(page);
});

test('file in deleted folder is absent from search and not added to archive', async ({
test('file in deleted folder is absent from search and added to archive', async ({
browser,
page,
}) => {
Expand Down Expand Up @@ -431,37 +431,75 @@ test.describe('Context Center - Folder Delete: file absent from search and archi
const { apiContext, afterAction } = await getDefaultAdminAPIContext(
browser
);
await waitForDocumentAbsentFromSearch(apiContext, documentFileName);
await afterAction();

const searchInput = getDocumentSearchInput(page);

await expect
.poll(
async () => {
const searchResPromise = page.waitForResponse(
(res) =>
res.url().includes('/api/v1/search/query') &&
res.url().includes('index=contextFile')
);
await searchInput.fill('');
await searchInput.fill(documentFileName);
await searchResPromise;
try {
// Poll the API directly. Clearing and immediately refilling the debounced UI input with
// the same final value does not issue a second request, so the previous implementation
// only checked the index once immediately after deletion and then waited for a response
// that could never arrive.
await expect
.poll(
async () => {
const response = await apiContext.get('/api/v1/search/query', {
params: {
q: documentFileName,
index: 'contextFile',
from: 0,
size: 10,
deleted: false,
},
});
expect(response.ok()).toBeTruthy();
const body = await response.json();

return (body?.hits?.hits ?? []).some(
(hit: { _id?: string; _source?: { id?: string } }) =>
hit._id === documentId || hit._source?.id === documentId
);
},
{
intervals: [3000, 5000, 10000],
message: `File ${documentFileName} still visible in search after its folder was deleted`,
timeout: 60000,
}
)
.toBe(false);
} finally {
await afterAction();
}

return getDocumentRowByName(page, documentFileName)
.isVisible()
.catch(() => false);
},
{
intervals: [3000, 5000, 10000],
message: `File ${documentFileName} still visible in search after its folder was deleted`,
timeout: 60000,
}
)
.toBe(false);
// Drive the debounced search state through a distinct value before restoring the
// document name. Clearing and refilling within one debounce window leaves the
// debounced value unchanged, so no request is emitted for the final fill.
const noMatchQuery = `deleted-folder-no-match-${uuid()}`;
const noMatchResPromise = page.waitForResponse((res) => {
const url = new URL(res.url());

return (
url.pathname.includes('/api/v1/search/query') &&
url.searchParams.get('index') === 'contextFile' &&
url.searchParams.get('q') === noMatchQuery
);
});
await searchInput.fill(noMatchQuery);
await noMatchResPromise;

const searchResPromise = page.waitForResponse((res) => {
const url = new URL(res.url());

return (
url.pathname.includes('/api/v1/search/query') &&
url.searchParams.get('index') === 'contextFile' &&
url.searchParams.get('q') === documentFileName
);
});
await searchInput.fill(documentFileName);
await searchResPromise;
await expect(getDocumentRowByName(page, documentFileName)).toBeHidden();
});

// ── 6. Archive page UI — file row is absent ───────────────────────────────
// ── 6. Archive page UI — soft-deleted file row is present ────────────────

await test.step('file should be visibile in the archive page', async () => {
const { apiContext, afterAction } = await getDefaultAdminAPIContext(
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -89,6 +89,7 @@
const RELATED_QUICK_LINK_URL = 'https://docs.open-metadata.org';
const UPDATED_QUICK_LINK_URL = 'https://docs.open-metadata.org/quick-link';
const MIN_CARDS = 10;
const ARTICLE_LIST_PAGE_SIZE = 25;

let DRAFT_ARTICLE_A_DISPLAY_NAME: string;
let DRAFT_ARTICLE_B_DISPLAY_NAME: string;
Expand Down Expand Up @@ -688,7 +689,7 @@
url.pathname.includes('/context-center/articles/')
);
await waitForAllLoadersToDisappear(page);
await page.waitForTimeout(500);

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

View workflow job for this annotation

GitHub Actions / checkstyle

Unexpected use of page.waitForTimeout()

await navigateToArticles(page);
const rightPanel = page.getByTestId('knowledge-center-right-panel');
Expand All @@ -713,18 +714,30 @@
const cards = listing.locator('[data-testid^="knowledge-card-"]');
const initialCardCount = await cards.count();

const observerElement = page.getByTestId('observer-element');
const paginationResponse = page.waitForResponse(
(response) =>
response.url().includes('/api/v1/contextCenter/pages') &&
response.url().includes('offset=')
);

await observerElement.scrollIntoViewIfNeeded();
await paginationResponse;
await waitForAllLoadersToDisappear(page);
if (initialCardCount > ARTICLE_LIST_PAGE_SIZE) {
// Returning from the recently-viewed article can preserve the list's
// scroll position. In that case the observer has already fetched one or
// more additional pages, which itself proves pagination occurred.
expect(initialCardCount).toBeGreaterThan(ARTICLE_LIST_PAGE_SIZE);
Comment on lines +717 to +721

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 Quality: Tautological assertion in eager-fetch pagination branch

In ContextCenterArticles.spec.ts the if (initialCardCount > ARTICLE_LIST_PAGE_SIZE) branch asserts expect(initialCardCount).toBeGreaterThan(ARTICLE_LIST_PAGE_SIZE), which is guaranteed true by the branch condition itself and verifies nothing about pagination. In this case the test provides no real coverage that additional pages were fetched. Consider asserting a concrete post-condition (e.g. that the card count exceeds one page's worth of items, or that at least one paginated fetch was observed) so the branch still validates the eager-fetch behavior described in the comment.

Was this helpful? React with 👍 / 👎

} else {
const observerElement = page.getByTestId('observer-element');
const paginationResponse = page.waitForResponse((response) => {
const url = new URL(response.url());

return (
response.request().method() === 'GET' &&
url.pathname === '/api/v1/contextCenter/pages' &&
url.searchParams.get('sortBy') === 'updatedAt' &&
Number(url.searchParams.get('offset')) > 0
);
});

expect(await cards.count()).toBeGreaterThan(initialCardCount);
await observerElement.scrollIntoViewIfNeeded();
const response = await paginationResponse;
expect(response.status()).toBe(200);
await waitForAllLoadersToDisappear(page);
expect(await cards.count()).toBeGreaterThan(initialCardCount);
}
});

test('Left hierarchy pagination and expand collapse actions work', async ({
Expand Down Expand Up @@ -1253,7 +1266,7 @@
.click();
await page.getByTestId('save').click();

await page.waitForSelector(

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

View workflow job for this annotation

GitHub Actions / checkstyle

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

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

View workflow job for this annotation

GitHub Actions / checkstyle

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

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -569,7 +569,14 @@
view.locator(`[data-testid="document-row-${outsideDoc.id}"]`)
).toBeVisible();

const browseResPromise = page.waitForResponse(
(res) =>
res.url().includes('/api/v1/contextCenter/drive/files') &&
!res.url().includes('search')
);
await searchInput.clear();
await browseResPromise;
await expect(searchInput).toHaveValue('');
await waitForAllLoadersToDisappear(page);
await selectFolderInSidebar(page, folderName);

Expand Down Expand Up @@ -1004,7 +1011,7 @@
const clipboardText = await copyAndGetClipboardText(page, copyBtn);
expect(clipboardText).toContain(`document=${doc.id}`);

const newTab = await browser.newPage();

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

View workflow job for this annotation

GitHub Actions / checkstyle

Prefer the `page` fixture (test.use({ storageState })) over browser.newPage() + manual login for single-user admin tests. For multi-user tests that need a second non-admin page, this warning is expected — no action needed
await newTab.goto(clipboardText);
await newTab
.getByTestId('context-center-documents-page')
Expand Down
Loading
Loading