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