mirror of
https://github.com/open-metadata/OpenMetadata.git
synced 2025-10-20 13:23:52 +00:00
Fix flaky activity feed tests (#23298)
This commit is contained in:
parent
e9da0444ed
commit
cfef0d1c80
@ -16,9 +16,11 @@ import { PersonaClass } from '../../support/persona/PersonaClass';
|
|||||||
import { UserClass } from '../../support/user/UserClass';
|
import { UserClass } from '../../support/user/UserClass';
|
||||||
import { REACTION_EMOJIS, reactOnFeed } from '../../utils/activityFeed';
|
import { REACTION_EMOJIS, reactOnFeed } from '../../utils/activityFeed';
|
||||||
import { performAdminLogin } from '../../utils/admin';
|
import { performAdminLogin } from '../../utils/admin';
|
||||||
import { redirectToHomePage, removeLandingBanner } from '../../utils/common';
|
import { redirectToHomePage } from '../../utils/common';
|
||||||
import { navigateToCustomizeLandingPage } from '../../utils/customizeLandingPage';
|
import {
|
||||||
import { selectPersona } from '../../utils/customizeNavigation';
|
navigateToCustomizeLandingPage,
|
||||||
|
setUserDefaultPersona,
|
||||||
|
} from '../../utils/customizeLandingPage';
|
||||||
|
|
||||||
const adminUser = new UserClass();
|
const adminUser = new UserClass();
|
||||||
const user1 = new UserClass();
|
const user1 = new UserClass();
|
||||||
@ -28,68 +30,64 @@ const testPersona = new PersonaClass();
|
|||||||
|
|
||||||
test.describe('FeedWidget on landing page', () => {
|
test.describe('FeedWidget on landing page', () => {
|
||||||
test.beforeAll(
|
test.beforeAll(
|
||||||
'setup: seed entities, users, create persona, customize widget, and create feed activity',
|
'setup: seed entities, users, create persona, and customize widget',
|
||||||
async ({ browser }) => {
|
async ({ browser }) => {
|
||||||
|
test.slow(true);
|
||||||
|
|
||||||
|
const { apiContext, afterAction } = await performAdminLogin(browser);
|
||||||
|
|
||||||
try {
|
try {
|
||||||
const { apiContext, afterAction } = await performAdminLogin(browser);
|
// Create users and entities
|
||||||
try {
|
await adminUser.create(apiContext);
|
||||||
// Create admin and a standard user
|
await adminUser.setAdminRole(apiContext);
|
||||||
await adminUser.create(apiContext);
|
await user1.create(apiContext);
|
||||||
await adminUser.setAdminRole(apiContext);
|
await seedEntity.create(apiContext);
|
||||||
await user1.create(apiContext);
|
await extraEntity.create(apiContext);
|
||||||
|
await testPersona.create(apiContext, [adminUser.responseData.id]);
|
||||||
|
|
||||||
// Create two entities to ensure feed diversity
|
// Set up widget in a separate page context
|
||||||
await seedEntity.create(apiContext);
|
|
||||||
await extraEntity.create(apiContext);
|
|
||||||
|
|
||||||
// Create a persona for testing
|
|
||||||
await testPersona.create(apiContext, [adminUser.responseData.id]);
|
|
||||||
} finally {
|
|
||||||
await afterAction();
|
|
||||||
}
|
|
||||||
|
|
||||||
// Log in as admin and customize the landing page for the persona
|
|
||||||
const adminPage = await browser.newPage();
|
const adminPage = await browser.newPage();
|
||||||
await adminUser.login(adminPage);
|
await adminUser.login(adminPage);
|
||||||
|
|
||||||
try {
|
try {
|
||||||
// Navigate to customize landing page for the persona
|
// Set persona as default
|
||||||
|
await redirectToHomePage(adminPage);
|
||||||
|
await setUserDefaultPersona(adminPage, testPersona.data.displayName);
|
||||||
|
|
||||||
|
// Navigate to customize landing page
|
||||||
await navigateToCustomizeLandingPage(adminPage, {
|
await navigateToCustomizeLandingPage(adminPage, {
|
||||||
personaName: testPersona.data.name,
|
personaName: testPersona.data.name,
|
||||||
});
|
});
|
||||||
|
|
||||||
// Find the Activity Feed widget and make it full size
|
// Ensure Activity Feed widget is full size
|
||||||
const activityFeedWidget = adminPage.locator(
|
const activityFeedWidget = adminPage.getByTestId(
|
||||||
'[data-testid="KnowledgePanel.ActivityFeed"]'
|
'KnowledgePanel.ActivityFeed'
|
||||||
);
|
);
|
||||||
|
|
||||||
// Click the more options button (three dots menu)
|
await expect(activityFeedWidget).toBeVisible();
|
||||||
const moreOptionsButton = activityFeedWidget.locator(
|
|
||||||
'[data-testid="more-options-button"]'
|
const moreOptionsButton = activityFeedWidget.getByTestId(
|
||||||
|
'more-options-button'
|
||||||
);
|
);
|
||||||
|
|
||||||
await expect(moreOptionsButton).toBeVisible();
|
|
||||||
|
|
||||||
await moreOptionsButton.click();
|
await moreOptionsButton.click();
|
||||||
|
|
||||||
// Click "Full Size" option from the dropdown menu
|
|
||||||
await adminPage.getByRole('menuitem', { name: 'Full Size' }).click();
|
await adminPage.getByRole('menuitem', { name: 'Full Size' }).click();
|
||||||
|
|
||||||
// Save the layout
|
// Save the layout if save button is enabled
|
||||||
await adminPage.locator('[data-testid="save-button"]').click();
|
const saveButton = adminPage.getByTestId('save-button');
|
||||||
await adminPage.waitForLoadState('networkidle');
|
if (await saveButton.isEnabled()) {
|
||||||
|
const saveResponse = adminPage.waitForResponse('/api/v1/docStore*');
|
||||||
|
await saveButton.click();
|
||||||
|
await adminPage.waitForLoadState('networkidle');
|
||||||
|
await saveResponse;
|
||||||
|
}
|
||||||
|
|
||||||
// Navigate back to home page
|
|
||||||
await redirectToHomePage(adminPage);
|
await redirectToHomePage(adminPage);
|
||||||
|
await adminPage.waitForLoadState('networkidle');
|
||||||
// Select the persona for the current user
|
|
||||||
await selectPersona(adminPage, testPersona);
|
|
||||||
} catch (e) {
|
|
||||||
// ignore failures here; tests have guards
|
|
||||||
} finally {
|
} finally {
|
||||||
await adminPage.close();
|
await adminPage.close();
|
||||||
}
|
}
|
||||||
} catch (e) {
|
} finally {
|
||||||
// proceed even if setup fails; tests handle empty state
|
await afterAction();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
);
|
);
|
||||||
@ -97,19 +95,16 @@ test.describe('FeedWidget on landing page', () => {
|
|||||||
test.afterAll(
|
test.afterAll(
|
||||||
'cleanup: delete entities, users, and persona',
|
'cleanup: delete entities, users, and persona',
|
||||||
async ({ browser }) => {
|
async ({ browser }) => {
|
||||||
|
const { apiContext, afterAction } = await performAdminLogin(browser);
|
||||||
|
|
||||||
try {
|
try {
|
||||||
const { apiContext, afterAction } = await performAdminLogin(browser);
|
await seedEntity.delete(apiContext);
|
||||||
try {
|
await extraEntity.delete(apiContext);
|
||||||
await seedEntity.delete(apiContext);
|
await user1.delete(apiContext);
|
||||||
await extraEntity.delete(apiContext);
|
await testPersona.delete(apiContext);
|
||||||
await user1.delete(apiContext);
|
await adminUser.delete(apiContext);
|
||||||
await adminUser.delete(apiContext);
|
} finally {
|
||||||
await testPersona.delete(apiContext);
|
await afterAction();
|
||||||
} finally {
|
|
||||||
await afterAction();
|
|
||||||
}
|
|
||||||
} catch (e) {
|
|
||||||
// ignore cleanup errors
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
);
|
);
|
||||||
@ -117,81 +112,72 @@ test.describe('FeedWidget on landing page', () => {
|
|||||||
test.beforeEach(async ({ page }) => {
|
test.beforeEach(async ({ page }) => {
|
||||||
await adminUser.login(page);
|
await adminUser.login(page);
|
||||||
await redirectToHomePage(page);
|
await redirectToHomePage(page);
|
||||||
await removeLandingBanner(page);
|
await page.waitForLoadState('networkidle');
|
||||||
});
|
});
|
||||||
|
|
||||||
test('renders widget wrapper and header with sort dropdown', async ({
|
test('renders widget wrapper and header with sort dropdown', async ({
|
||||||
page,
|
page,
|
||||||
}) => {
|
}) => {
|
||||||
const widget = page.locator('[data-testid="KnowledgePanel.ActivityFeed"]');
|
const widget = page.getByTestId('KnowledgePanel.ActivityFeed');
|
||||||
|
|
||||||
await expect(widget).toBeVisible();
|
await expect(widget).toBeVisible();
|
||||||
|
|
||||||
// Header title and icon
|
// Header verification
|
||||||
const header = widget.locator('[data-testid="widget-header"]');
|
const header = widget.getByTestId('widget-header');
|
||||||
|
|
||||||
await expect(header).toBeVisible();
|
await expect(header).toBeVisible();
|
||||||
await expect(header).toContainText('Activity Feed');
|
await expect(header).toContainText('Activity Feed');
|
||||||
|
|
||||||
// Sort dropdown should be visible (non-edit view)
|
// Sort dropdown verification
|
||||||
const sortDropdown = header.locator(
|
const sortDropdown = header.getByTestId('widget-sort-by-dropdown');
|
||||||
'[data-testid="widget-sort-by-dropdown"]'
|
|
||||||
);
|
|
||||||
|
|
||||||
await expect(sortDropdown).toBeVisible();
|
await expect(sortDropdown).toBeVisible();
|
||||||
|
|
||||||
// Open dropdown and verify options
|
// Test dropdown options
|
||||||
await sortDropdown.click();
|
await sortDropdown.click();
|
||||||
|
await page.waitForSelector('.ant-dropdown', { state: 'visible' });
|
||||||
|
|
||||||
await expect(
|
await expect(
|
||||||
page.getByRole('menuitem', {
|
page.getByRole('menuitem', { name: 'All Activity' })
|
||||||
name: 'All Activity',
|
|
||||||
})
|
|
||||||
).toBeVisible();
|
).toBeVisible();
|
||||||
|
await expect(page.getByRole('menuitem', { name: 'My Data' })).toBeVisible();
|
||||||
await expect(
|
await expect(
|
||||||
page.getByRole('menuitem', {
|
page.getByRole('menuitem', { name: 'Following' })
|
||||||
name: 'My Data',
|
|
||||||
})
|
|
||||||
).toBeVisible();
|
|
||||||
await expect(
|
|
||||||
page.getByRole('menuitem', {
|
|
||||||
name: 'Following',
|
|
||||||
})
|
|
||||||
).toBeVisible();
|
).toBeVisible();
|
||||||
|
|
||||||
// Close dropdown
|
// Close dropdown by clicking outside
|
||||||
await page.keyboard.press('Escape');
|
await widget.click();
|
||||||
|
|
||||||
|
await expect(page.locator('.ant-dropdown')).not.toBeVisible();
|
||||||
});
|
});
|
||||||
|
|
||||||
test('clicking title navigates to Explore', async ({ page }) => {
|
test('clicking title navigates to explore page', async ({ page }) => {
|
||||||
const widget = page.locator('[data-testid="KnowledgePanel.ActivityFeed"]');
|
const widget = page.getByTestId('KnowledgePanel.ActivityFeed');
|
||||||
|
|
||||||
await expect(widget).toBeVisible();
|
await expect(widget).toBeVisible();
|
||||||
|
|
||||||
// Click the header title to navigate to Explore
|
// Click the title to navigate
|
||||||
await widget
|
const titleLink = widget
|
||||||
.locator('[data-testid="widget-header"]')
|
.getByTestId('widget-header')
|
||||||
.getByText('Activity Feed')
|
.getByText('Activity Feed');
|
||||||
.click();
|
await titleLink.click();
|
||||||
await page.waitForLoadState('networkidle');
|
await page.waitForLoadState('networkidle');
|
||||||
|
|
||||||
await expect(page).toHaveURL(/\/explore/);
|
// Verify navigation to explore
|
||||||
|
await expect(page.url()).toContain('/explore');
|
||||||
// Navigate back home to keep context consistent for next tests
|
|
||||||
await redirectToHomePage(page);
|
|
||||||
});
|
});
|
||||||
|
|
||||||
test('feed body renders list or empty state', async ({ page }) => {
|
test('feed body renders content or empty state', async ({ page }) => {
|
||||||
const widget = page.locator('[data-testid="KnowledgePanel.ActivityFeed"]');
|
const widget = page.getByTestId('KnowledgePanel.ActivityFeed');
|
||||||
|
|
||||||
await expect(widget).toBeVisible();
|
await expect(widget).toBeVisible();
|
||||||
|
|
||||||
// Feed container
|
// Wait for feed content to load
|
||||||
const container = page.locator('#feedWidgetData');
|
const container = page.locator('#feedWidgetData');
|
||||||
|
|
||||||
await expect(container).toBeVisible();
|
await expect(container).toBeVisible();
|
||||||
|
|
||||||
// Either render feed messages or show the widget-level empty state
|
// Check for either content or empty state
|
||||||
const messageContainers = container.locator(
|
const messageContainers = container.locator(
|
||||||
'[data-testid="message-container"]'
|
'[data-testid="message-container"]'
|
||||||
);
|
);
|
||||||
@ -206,90 +192,87 @@ test.describe('FeedWidget on landing page', () => {
|
|||||||
});
|
});
|
||||||
|
|
||||||
test('changing filter triggers feed reload', async ({ page }) => {
|
test('changing filter triggers feed reload', async ({ page }) => {
|
||||||
const widget = page.locator('[data-testid="KnowledgePanel.ActivityFeed"]');
|
const widget = page.getByTestId('KnowledgePanel.ActivityFeed');
|
||||||
|
|
||||||
await expect(widget).toBeVisible();
|
await expect(widget).toBeVisible();
|
||||||
|
|
||||||
const sortDropdown = widget.locator(
|
const sortDropdown = widget.getByTestId('widget-sort-by-dropdown');
|
||||||
'[data-testid="widget-sort-by-dropdown"]'
|
|
||||||
);
|
|
||||||
|
|
||||||
await expect(sortDropdown).toBeVisible();
|
await expect(sortDropdown).toBeVisible();
|
||||||
|
|
||||||
// Switch to My Data and wait for a feed API call
|
// Switch to My Data filter
|
||||||
await sortDropdown.click();
|
await sortDropdown.click();
|
||||||
const myData = page.getByRole('menuitem', {
|
await page.waitForSelector('.ant-dropdown', { state: 'visible' });
|
||||||
name: 'My Data',
|
|
||||||
});
|
const myDataOption = page.getByRole('menuitem', { name: 'My Data' });
|
||||||
if ((await myData.count()) > 0) {
|
|
||||||
const feedReq = page.waitForResponse(/\/api\/v1\/feed.*/);
|
const feedResponse = page.waitForResponse('/api/v1/feed*');
|
||||||
await myData.click();
|
await myDataOption.click();
|
||||||
await feedReq;
|
await page.waitForLoadState('networkidle');
|
||||||
}
|
await feedResponse;
|
||||||
|
|
||||||
// Switch back to All Activity
|
// Switch back to All Activity
|
||||||
await sortDropdown.click();
|
await sortDropdown.click();
|
||||||
const allActivity = page.getByRole('button', {
|
await page.waitForSelector('.ant-dropdown', { state: 'visible' });
|
||||||
|
|
||||||
|
const allActivityOption = page.getByRole('menuitem', {
|
||||||
name: 'All Activity',
|
name: 'All Activity',
|
||||||
});
|
});
|
||||||
if ((await allActivity.count()) > 0) {
|
if (await allActivityOption.isVisible()) {
|
||||||
const feedReq = page.waitForResponse(/\/api\/v1\/feed.*/);
|
const feedResponse = page.waitForResponse('/api/v1/feed*');
|
||||||
await allActivity.click();
|
await allActivityOption.click();
|
||||||
await feedReq;
|
await page.waitForLoadState('networkidle');
|
||||||
|
await feedResponse;
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
test('footer shows View More when applicable and navigates', async ({
|
test('footer shows view more link when applicable', async ({ page }) => {
|
||||||
page,
|
const widget = page.getByTestId('KnowledgePanel.ActivityFeed');
|
||||||
}) => {
|
|
||||||
const widget = page.locator('[data-testid="KnowledgePanel.ActivityFeed"]');
|
|
||||||
|
|
||||||
await expect(widget).toBeVisible();
|
await expect(widget).toBeVisible();
|
||||||
|
|
||||||
// Footer only renders when showMoreButton is true
|
// Check if View More link exists
|
||||||
const viewMore = widget.getByRole('link', { name: /View More/i });
|
const viewMoreLink = widget.getByRole('link', { name: /View More/i });
|
||||||
if ((await viewMore.count()) > 0) {
|
|
||||||
await expect(viewMore).toBeVisible();
|
|
||||||
|
|
||||||
await viewMore.click();
|
await expect(viewMoreLink).toBeVisible();
|
||||||
await page.waitForLoadState('networkidle');
|
|
||||||
|
|
||||||
// We should land on user Activity Feed. We just verify navigation happened
|
// Click and verify navigation
|
||||||
await expect(page).not.toHaveURL(/home|welcome/i);
|
await viewMoreLink.click();
|
||||||
|
await page.waitForLoadState('networkidle');
|
||||||
|
|
||||||
// Return home for subsequent tests
|
// Should navigate away from home page
|
||||||
await redirectToHomePage(page);
|
expect(page.url()).not.toMatch(/home|welcome/i);
|
||||||
}
|
|
||||||
});
|
});
|
||||||
|
|
||||||
test('renders feed cards via ActivityFeedListV1New in widget mode', async ({
|
test('feed cards render with proper structure when available', async ({
|
||||||
page,
|
page,
|
||||||
}) => {
|
}) => {
|
||||||
const container = page.locator('#feedWidgetData');
|
const container = page.locator('#feedWidgetData');
|
||||||
|
|
||||||
await expect(container).toBeVisible();
|
await expect(container).toBeVisible();
|
||||||
|
|
||||||
const firstCard = container
|
const messageContainers = container.locator(
|
||||||
.locator('[data-testid="message-container"]')
|
'[data-testid="message-container"]'
|
||||||
.first();
|
);
|
||||||
|
|
||||||
if ((await firstCard.count()) > 0) {
|
const firstCard = messageContainers.first();
|
||||||
await expect(firstCard).toBeVisible();
|
|
||||||
|
|
||||||
// Typical elements within a compact feed card rendered in widget mode
|
await expect(firstCard).toBeVisible();
|
||||||
const headerText = firstCard.locator('[data-testid="headerText"]');
|
|
||||||
const timestamp = firstCard.locator('[data-testid="timestamp"]');
|
|
||||||
|
|
||||||
if ((await headerText.count()) > 0) {
|
// Verify typical feed card elements
|
||||||
await expect(headerText).toBeVisible();
|
const headerText = firstCard.locator('[data-testid="headerText"]');
|
||||||
}
|
const timestamp = firstCard.locator('[data-testid="timestamp"]');
|
||||||
if ((await timestamp.count()) > 0) {
|
|
||||||
await expect(timestamp).toBeVisible();
|
// Check elements exist if available
|
||||||
}
|
if ((await headerText.count()) > 0) {
|
||||||
|
await expect(headerText).toBeVisible();
|
||||||
|
}
|
||||||
|
if ((await timestamp.count()) > 0) {
|
||||||
|
await expect(timestamp).toBeVisible();
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
test('emoji reactions can be added and removed in widget feed cards', async ({
|
test('emoji reactions can be added when feed messages exist', async ({
|
||||||
page,
|
page,
|
||||||
}) => {
|
}) => {
|
||||||
const messages = page.locator('[data-testid="message-container"]');
|
const messages = page.locator('[data-testid="message-container"]');
|
||||||
@ -327,6 +310,8 @@ test.describe('FeedWidget on landing page', () => {
|
|||||||
page,
|
page,
|
||||||
}) => {
|
}) => {
|
||||||
const messages = page.locator('[data-testid="message-container"]');
|
const messages = page.locator('[data-testid="message-container"]');
|
||||||
|
|
||||||
|
// Skip if no messages available
|
||||||
if ((await messages.count()) === 0) {
|
if ((await messages.count()) === 0) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
@ -335,31 +320,44 @@ test.describe('FeedWidget on landing page', () => {
|
|||||||
|
|
||||||
await expect(firstMessage).toBeVisible();
|
await expect(firstMessage).toBeVisible();
|
||||||
|
|
||||||
// Open thread/drawer via reply count or clicking the card
|
// Open thread drawer via reply count or clicking the card
|
||||||
const replyCountBtn = firstMessage.locator('[data-testid="reply-count"]');
|
const replyCountBtn = firstMessage.locator('[data-testid="reply-count"]');
|
||||||
if (await replyCountBtn.count()) {
|
|
||||||
|
if ((await replyCountBtn.count()) > 0) {
|
||||||
await replyCountBtn.click();
|
await replyCountBtn.click();
|
||||||
} else {
|
} else {
|
||||||
await firstMessage.click();
|
await firstMessage.click();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Wait for drawer to appear
|
||||||
const drawer = page.locator('.ant-drawer-content');
|
const drawer = page.locator('.ant-drawer-content');
|
||||||
|
|
||||||
await expect(drawer).toBeVisible();
|
await expect(drawer).toBeVisible();
|
||||||
|
|
||||||
// Type a quick reply if editor is present
|
// Try to post a reply if comment input is available
|
||||||
const commentInput = drawer.locator('[data-testid="comments-input-field"]');
|
const commentInput = drawer.locator('[data-testid="comments-input-field"]');
|
||||||
|
|
||||||
if (await commentInput.count()) {
|
if (await commentInput.count()) {
|
||||||
await commentInput.click();
|
await commentInput.click();
|
||||||
await page.fill(
|
await page.waitForLoadState('networkidle');
|
||||||
'[data-testid="editor-wrapper"] .ql-editor',
|
|
||||||
'Widget thread automated reply'
|
|
||||||
);
|
|
||||||
|
|
||||||
const sendReply = page.waitForResponse(/\/api\/v1\/feed\/.*\/posts/);
|
// Fill in the editor
|
||||||
await page.getByTestId('send-button').click({ force: true });
|
const editorField = page.locator(
|
||||||
|
'[data-testid="editor-wrapper"] .ql-editor'
|
||||||
|
);
|
||||||
|
await editorField.fill('Widget thread automated reply');
|
||||||
|
|
||||||
|
// Wait for send button to be enabled and send reply
|
||||||
|
const sendButton = page.getByTestId('send-button');
|
||||||
|
|
||||||
|
await expect(sendButton).toBeEnabled();
|
||||||
|
|
||||||
|
const sendReply = page.waitForResponse('/api/v1/feed/*/posts');
|
||||||
|
await page.waitForLoadState('networkidle');
|
||||||
|
await sendButton.click();
|
||||||
await sendReply;
|
await sendReply;
|
||||||
|
|
||||||
|
// Verify reply appears
|
||||||
await expect(
|
await expect(
|
||||||
drawer.locator('[data-testid="feed-replies"]')
|
drawer.locator('[data-testid="feed-replies"]')
|
||||||
).toContainText('Widget thread automated reply');
|
).toContainText('Widget thread automated reply');
|
||||||
@ -372,5 +370,8 @@ test.describe('FeedWidget on landing page', () => {
|
|||||||
} else {
|
} else {
|
||||||
await page.keyboard.press('Escape');
|
await page.keyboard.press('Escape');
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Verify drawer is closed
|
||||||
|
await expect(drawer).not.toBeVisible();
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
Loading…
x
Reference in New Issue
Block a user