Fix flaky activity feed tests (#23298)

This commit is contained in:
Harshit Shah 2025-09-09 12:05:20 +05:30 committed by Harshit Shah
parent e9da0444ed
commit cfef0d1c80

View File

@ -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();
}); });
}); });