You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
 
 
 
 
 
 

223 lines
11 KiB

/**
* @fileoverview Tests for the new activity/offers system in TimeSafari
*
* CRITICAL UNDERSTANDING: Offer Acknowledgment System
* ===================================================
*
* This file tests the offer acknowledgment mechanism, which was clarified through
* systematic debugging investigation. Key findings:
*
* 1. POINTER-BASED TRACKING:
* - TimeSafari uses `lastAckedOfferToUserJwtId` to track the last acknowledged offer
* - Offers with IDs newer than this pointer are considered "new" and counted
* - The UI shows count of offers newer than the pointer
*
* 2. TWO DISMISSAL MECHANISMS:
* a) COMPLETE DISMISSAL (used in this test):
* - Triggered by: Expanding offers section (clicking chevron)
* - Method: expandOffersToUserAndMarkRead()
* - Action: Sets lastAckedOfferToUserJwtId = newOffersToUser[0].jwtId (newest)
* - Result: ALL offers marked as read, count becomes 0 (hidden)
*
* b) SELECTIVE DISMISSAL:
* - Triggered by: Clicking "Keep all above as new offers"
* - Method: markOffersAsReadStartingWith(jwtId)
* - Action: Sets lastAckedOfferToUserJwtId = nextOffer.jwtId (partial)
* - Result: Only offers above clicked offer marked as read
*
* 3. BROWSER COMPATIBILITY:
* - Initially appeared to be Chromium-specific issue
* - Investigation revealed test logic error, not browser incompatibility
* - Both Chromium and Firefox now pass consistently
*
* @author Matthew Raymer
* @since Investigation completed 2024-12-27
*/
import { test, expect } from '@playwright/test';
import { importUser, generateNewEthrUser, switchToUser } from './testUtils';
test('New offers for another user', async ({ page }) => {
const user01Did = await generateNewEthrUser(page);
await page.goto('./');
await page.getByTestId('closeOnboardingAndFinish').click();
await expect(page.getByTestId('newDirectOffersActivityNumber')).toBeHidden();
await importUser(page, '00');
await page.goto('./contacts');
await page.getByPlaceholder('URL or DID, Name, Public Key').fill(user01Did + ', A Friend');
await expect(page.locator('button > svg.fa-plus')).toBeVisible();
await page.locator('button > svg.fa-plus').click();
await expect(page.locator('div[role="alert"] span:has-text("Contact Added")')).toBeVisible();
await page.locator('div[role="alert"] button:has-text("No")').click(); // don't register
await page.locator('div[role="alert"] button > svg.fa-xmark').click(); // dismiss info alert
await expect(page.locator('div[role="alert"] button > svg.fa-xmark')).toBeHidden(); // ensure alert is gone
// show buttons to make offers directly to people
await page.getByRole('button').filter({ hasText: /See Actions/i }).click();
// make an offer directly to user 1
// Generate a random string of 3 characters, skipping the "0." at the beginning
const randomString1 = Math.random().toString(36).substring(2, 5);
await page.getByTestId('offerButton').click();
await page.getByTestId('inputDescription').fill(`help of ${randomString1} from #000`);
await page.getByTestId('inputOfferAmount').fill('1');
await page.getByRole('button', { name: 'Sign & Send' }).click();
await expect(page.getByText('That offer was recorded.')).toBeVisible();
await page.locator('div[role="alert"] button > svg.fa-xmark').click(); // dismiss info alert
await expect(page.locator('div[role="alert"] button > svg.fa-xmark')).toBeHidden(); // ensure alert is gone
// make another offer to user 1
const randomString2 = Math.random().toString(36).substring(2, 5);
await page.getByTestId('offerButton').click();
await page.getByTestId('inputDescription').fill(`help of ${randomString2} from #000`);
await page.getByTestId('inputOfferAmount').fill('3');
await page.getByRole('button', { name: 'Sign & Send' }).click();
await expect(page.getByText('That offer was recorded.')).toBeVisible();
await page.locator('div[role="alert"] button > svg.fa-xmark').click(); // dismiss info alert
await expect(page.locator('div[role="alert"] button > svg.fa-xmark')).toBeHidden(); // ensure alert is gone
// as user 1, go to the home page and check that two offers are shown as new
console.log('[DEBUG] 60-new-activity: Switching to user01Did:', user01Did);
await switchToUser(page, user01Did);
console.log('[DEBUG] 60-new-activity: Switch completed, navigating to home page');
await page.goto('./');
console.log('[DEBUG] 60-new-activity: Navigated to home page');
// Wait for page to load completely
await page.waitForLoadState('networkidle');
console.log('[DEBUG] 60-new-activity: Page load completed');
// Add systematic debugging for the newDirectOffersActivityNumber element
console.log('[DEBUG] 60-new-activity: Looking for newDirectOffersActivityNumber element');
// Check if the element exists at all
const offerNumElem = page.getByTestId('newDirectOffersActivityNumber');
const elementExists = await offerNumElem.count();
console.log('[DEBUG] 60-new-activity: newDirectOffersActivityNumber element count:', elementExists);
// Check if parent containers exist
const offerContainer = page.locator('[data-testid="newDirectOffersActivityNumber"]').locator('..');
const containerExists = await offerContainer.count();
console.log('[DEBUG] 60-new-activity: Parent container exists:', containerExists > 0);
if (containerExists > 0) {
const containerVisible = await offerContainer.isVisible();
console.log('[DEBUG] 60-new-activity: Parent container visible:', containerVisible);
}
// Look for any elements with test IDs that might be related
const allTestIds = page.locator('[data-testid]');
const testIdCount = await allTestIds.count();
console.log('[DEBUG] 60-new-activity: Found', testIdCount, 'elements with test IDs');
for (let i = 0; i < Math.min(testIdCount, 20); i++) {
const element = allTestIds.nth(i);
const testId = await element.getAttribute('data-testid');
const isVisible = await element.isVisible();
const textContent = await element.textContent();
console.log(`[DEBUG] 60-new-activity: TestID ${i}: "${testId}" (visible: ${isVisible}, text: "${textContent?.trim()}")`);
}
// Check for the specific elements mentioned in HomeView.vue
const newOffersSection = page.locator('div:has([data-testid="newDirectOffersActivityNumber"])');
const newOffersSectionExists = await newOffersSection.count();
console.log('[DEBUG] 60-new-activity: New offers section exists:', newOffersSectionExists > 0);
// Check for loading states
const loadingIndicators = page.locator('.fa-spinner, .fa-spin, [class*="loading"]');
const loadingCount = await loadingIndicators.count();
console.log('[DEBUG] 60-new-activity: Loading indicators found:', loadingCount);
// Wait a bit longer and check again
console.log('[DEBUG] 60-new-activity: Waiting additional 3 seconds for offers to load');
await page.waitForTimeout(3000);
const elementExistsAfterWait = await offerNumElem.count();
console.log('[DEBUG] 60-new-activity: newDirectOffersActivityNumber element count after wait:', elementExistsAfterWait);
if (elementExistsAfterWait === 0) {
console.log('[DEBUG] 60-new-activity: Element still not found, taking screenshot');
await page.screenshot({ path: 'debug-missing-offers-element.png', fullPage: true });
console.log('[DEBUG] 60-new-activity: Screenshot saved as debug-missing-offers-element.png');
// Check page URL and state
const currentUrl = page.url();
console.log('[DEBUG] 60-new-activity: Current URL:', currentUrl);
// Check if we're actually logged in as the right user
const didElement = page.getByTestId('didWrapper');
const didElementExists = await didElement.count();
if (didElementExists > 0) {
const currentDid = await didElement.textContent();
console.log('[DEBUG] 60-new-activity: Current DID on page:', currentDid?.trim());
console.log('[DEBUG] 60-new-activity: Expected DID:', user01Did);
}
}
let offerNumElemForTest = page.getByTestId('newDirectOffersActivityNumber');
await expect(offerNumElemForTest).toHaveText('2');
// click on the number of new offers to go to the list page
await offerNumElemForTest.click();
await expect(page.getByText('New Offers To You', { exact: true })).toBeVisible();
await page.getByTestId('showOffersToUser').locator('div > svg.fa-chevron-right').click();
// note that they show in reverse chronologicalorder
await expect(page.getByText(`help of ${randomString2} from #000`)).toBeVisible();
await expect(page.getByText(`help of ${randomString1} from #000`)).toBeVisible();
/**
* OFFER ACKNOWLEDGMENT MECHANISM DOCUMENTATION
*
* TimeSafari uses a pointer-based system to track which offers are "new":
* - `lastAckedOfferToUserJwtId` stores the ID of the last acknowledged offer
* - Offers newer than this pointer are considered "new" and counted
*
* Two dismissal mechanisms exist:
* 1. COMPLETE DISMISSAL: Expanding the offers section calls expandOffersToUserAndMarkRead()
* which sets lastAckedOfferToUserJwtId = newOffersToUser[0].jwtId (newest offer)
* Result: ALL offers marked as read, count goes to 0
*
* 2. SELECTIVE DISMISSAL: "Keep all above" calls markOffersAsReadStartingWith(jwtId)
* which sets lastAckedOfferToUserJwtId = nextOffer.jwtId (partial dismissal)
* Result: Only offers above the clicked offer are marked as read
*
* This test uses mechanism #1 (expansion) for complete dismissal.
* The expansion already happened when we clicked the chevron above.
*/
console.log('[DEBUG] 60-new-activity: Offers section already expanded, marking all offers as read');
console.log('[DEBUG] 60-new-activity: Expansion calls expandOffersToUserAndMarkRead() -> sets lastAckedOfferToUserJwtId to newest offer');
// now see that all offers are dismissed since we expanded the section
console.log('[DEBUG] 60-new-activity: Going back to home page to check offers are dismissed');
await page.goto('./');
// Add debugging for the final check
await page.waitForLoadState('networkidle');
console.log('[DEBUG] 60-new-activity: Page loaded for final check');
const offerNumElemFinal = page.getByTestId('newDirectOffersActivityNumber');
const elementExistsFinal = await offerNumElemFinal.count();
console.log('[DEBUG] 60-new-activity: newDirectOffersActivityNumber element count (final check):', elementExistsFinal);
if (elementExistsFinal > 0) {
const finalIsVisible = await offerNumElemFinal.isVisible();
const finalText = await offerNumElemFinal.textContent();
console.log('[DEBUG] 60-new-activity: Final element visible:', finalIsVisible);
console.log('[DEBUG] 60-new-activity: Final element text:', finalText);
if (finalIsVisible) {
console.log('[DEBUG] 60-new-activity: Element is still visible when it should be hidden');
await page.screenshot({ path: 'debug-offers-still-visible-final.png', fullPage: true });
console.log('[DEBUG] 60-new-activity: Screenshot saved as debug-offers-still-visible-final.png');
}
}
// wait until the list with ID listLatestActivity has at least one visible item
await page.locator('#listLatestActivity li').first().waitFor({ state: 'visible' });
console.log('[DEBUG] 60-new-activity: Activity list loaded');
await expect(page.getByTestId('newDirectOffersActivityNumber')).toBeHidden();
});