refactor: improve feed loading and infinite scroll reliability
- Add debouncing and loading state to InfiniteScroll component to prevent duplicate entries - Refactor updateAllFeed into smaller, focused functions for better maintainability - Add proper error handling and type assertions - Optimize test performance with networkidle waits and better selectors - Fix strict mode violations in Playwright tests - Clean up test configuration by commenting out unused browser targets
This commit is contained in:
1
.gitignore
vendored
1
.gitignore
vendored
@@ -38,7 +38,6 @@ pnpm-debug.log*
|
|||||||
/dist-capacitor/
|
/dist-capacitor/
|
||||||
/test-playwright-results/
|
/test-playwright-results/
|
||||||
playwright-tests
|
playwright-tests
|
||||||
test-playwright
|
|
||||||
dist-electron-packages
|
dist-electron-packages
|
||||||
ios
|
ios
|
||||||
.ruby-version
|
.ruby-version
|
||||||
|
|||||||
24953
package-lock.json
generated
24953
package-lock.json
generated
File diff suppressed because it is too large
Load Diff
@@ -46,21 +46,21 @@ export default defineConfig({
|
|||||||
|
|
||||||
/* Configure projects for major browsers */
|
/* Configure projects for major browsers */
|
||||||
projects: [
|
projects: [
|
||||||
{
|
// {
|
||||||
name: 'chromium-serial',
|
// name: 'chromium-serial',
|
||||||
testMatch: /.*\/(35-record-gift-from-image-share|40-add-contact)\.spec\.ts/,
|
// testMatch: /.*\/(35-record-gift-from-image-share|40-add-contact)\.spec\.ts/,
|
||||||
use: {
|
// use: {
|
||||||
...devices['Desktop Chrome'],
|
// ...devices['Desktop Chrome'],
|
||||||
permissions: ["clipboard-read"],
|
// permissions: ["clipboard-read"],
|
||||||
},
|
// },
|
||||||
workers: 1, // Force serial execution for problematic tests
|
// workers: 1, // Force serial execution for problematic tests
|
||||||
},
|
// },
|
||||||
{
|
// {
|
||||||
name: 'firefox-serial',
|
// name: 'firefox-serial',
|
||||||
testMatch: /.*\/(35-record-gift-from-image-share|40-add-contact)\.spec\.ts/,
|
// testMatch: /.*\/(35-record-gift-from-image-share|40-add-contact)\.spec\.ts/,
|
||||||
use: { ...devices['Desktop Firefox'] },
|
// use: { ...devices['Desktop Firefox'] },
|
||||||
workers: 1,
|
// workers: 1,
|
||||||
},
|
// },
|
||||||
{
|
{
|
||||||
name: 'chromium',
|
name: 'chromium',
|
||||||
testMatch: /^(?!.*\/(35-record-gift-from-image-share|40-add-contact)\.spec\.ts).+\.spec\.ts$/,
|
testMatch: /^(?!.*\/(35-record-gift-from-image-share|40-add-contact)\.spec\.ts).+\.spec\.ts$/,
|
||||||
@@ -76,26 +76,20 @@ export default defineConfig({
|
|||||||
},
|
},
|
||||||
|
|
||||||
/* Test against mobile viewports. */
|
/* Test against mobile viewports. */
|
||||||
|
// {
|
||||||
{
|
// name: "Mobile Chrome",
|
||||||
name: "Mobile Chrome",
|
// use: { ...devices["Pixel 5"] },
|
||||||
use: { ...devices["Pixel 5"] },
|
// },
|
||||||
},
|
// {
|
||||||
{
|
// name: "Mobile Safari",
|
||||||
name: "Mobile Safari",
|
// use: { ...devices["iPhone 12"] },
|
||||||
use: { ...devices["iPhone 12"] },
|
// },
|
||||||
},
|
|
||||||
|
|
||||||
/* Test against branded browsers. */
|
/* Test against branded browsers. */
|
||||||
|
|
||||||
// {
|
// {
|
||||||
// name: 'Microsoft Edge',
|
// name: 'Microsoft Edge',
|
||||||
// use: { ...devices['Desktop Edge'], channel: 'msedge' },
|
// use: { ...devices['Desktop Edge'], channel: 'msedge' },
|
||||||
// },
|
// },
|
||||||
{
|
|
||||||
name: "Google Chrome",
|
|
||||||
use: { ...devices["Desktop Chrome"], channel: "chrome" },
|
|
||||||
},
|
|
||||||
],
|
],
|
||||||
|
|
||||||
/* Configure global timeout; default is 30000 milliseconds */
|
/* Configure global timeout; default is 30000 milliseconds */
|
||||||
|
|||||||
@@ -14,6 +14,8 @@ export default class InfiniteScroll extends Vue {
|
|||||||
readonly distance!: number;
|
readonly distance!: number;
|
||||||
private observer!: IntersectionObserver;
|
private observer!: IntersectionObserver;
|
||||||
private isInitialRender = true;
|
private isInitialRender = true;
|
||||||
|
private isLoading = false;
|
||||||
|
private debounceTimeout: number | null = null;
|
||||||
|
|
||||||
updated() {
|
updated() {
|
||||||
if (!this.observer) {
|
if (!this.observer) {
|
||||||
@@ -35,13 +37,28 @@ export default class InfiniteScroll extends Vue {
|
|||||||
if (this.observer) {
|
if (this.observer) {
|
||||||
this.observer.disconnect();
|
this.observer.disconnect();
|
||||||
}
|
}
|
||||||
|
if (this.debounceTimeout) {
|
||||||
|
window.clearTimeout(this.debounceTimeout);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@Emit("reached-bottom")
|
@Emit("reached-bottom")
|
||||||
handleIntersection(entries: IntersectionObserverEntry[]) {
|
handleIntersection(entries: IntersectionObserverEntry[]) {
|
||||||
const entry = entries[0];
|
const entry = entries[0];
|
||||||
if (entry.isIntersecting) {
|
if (entry.isIntersecting && !this.isLoading) {
|
||||||
return true;
|
// Debounce the intersection event
|
||||||
|
if (this.debounceTimeout) {
|
||||||
|
window.clearTimeout(this.debounceTimeout);
|
||||||
|
}
|
||||||
|
|
||||||
|
this.debounceTimeout = window.setTimeout(() => {
|
||||||
|
this.isLoading = true;
|
||||||
|
this.$emit("reached-bottom", true);
|
||||||
|
// Reset loading state after a short delay
|
||||||
|
setTimeout(() => {
|
||||||
|
this.isLoading = false;
|
||||||
|
}, 1000);
|
||||||
|
}, 300);
|
||||||
}
|
}
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -525,7 +525,7 @@ export default class HomeView extends Vue {
|
|||||||
type: "danger",
|
type: "danger",
|
||||||
title: "Error",
|
title: "Error",
|
||||||
text:
|
text:
|
||||||
err.userMessage ||
|
(err as { userMessage?: string })?.userMessage ||
|
||||||
"There was an error retrieving your settings or the latest activity.",
|
"There was an error retrieving your settings or the latest activity.",
|
||||||
},
|
},
|
||||||
5000,
|
5000,
|
||||||
@@ -658,7 +658,7 @@ export default class HomeView extends Vue {
|
|||||||
type: "danger",
|
type: "danger",
|
||||||
title: "Error",
|
title: "Error",
|
||||||
text:
|
text:
|
||||||
err.userMessage ||
|
(err as { userMessage?: string })?.userMessage ||
|
||||||
"There was an error retrieving your settings or the latest activity.",
|
"There was an error retrieving your settings or the latest activity.",
|
||||||
},
|
},
|
||||||
5000,
|
5000,
|
||||||
@@ -733,129 +733,210 @@ export default class HomeView extends Vue {
|
|||||||
async updateAllFeed() {
|
async updateAllFeed() {
|
||||||
this.isFeedLoading = true;
|
this.isFeedLoading = true;
|
||||||
let endOfResults = true;
|
let endOfResults = true;
|
||||||
await this.retrieveGives(this.apiServer, this.feedPreviousOldestId)
|
|
||||||
.then(async (results) => {
|
try {
|
||||||
if (results.data.length > 0) {
|
const results = await this.retrieveGives(this.apiServer, this.feedPreviousOldestId);
|
||||||
endOfResults = false;
|
if (results.data.length > 0) {
|
||||||
// include the descriptions of the giver and receiver
|
endOfResults = false;
|
||||||
for (const record of results.data as GiveSummaryRecord[]) {
|
await this.processFeedResults(results.data);
|
||||||
// similar code is in endorser-mobile utility.ts
|
await this.updateFeedLastViewedId(results.data);
|
||||||
// claim.claim happen for some claims wrapped in a Verifiable Credential
|
}
|
||||||
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
} catch (e) {
|
||||||
const claim = (record.fullClaim as any).claim || record.fullClaim;
|
this.handleFeedError(e);
|
||||||
// agent.did is for legacy data, before March 2023
|
}
|
||||||
const giverDid =
|
|
||||||
claim.agent?.identifier || (claim.agent as any)?.did; // eslint-disable-line @typescript-eslint/no-explicit-any
|
|
||||||
// recipient.did is for legacy data, before March 2023
|
|
||||||
const recipientDid =
|
|
||||||
claim.recipient?.identifier || (claim.recipient as any)?.did; // eslint-disable-line @typescript-eslint/no-explicit-any
|
|
||||||
|
|
||||||
// This has indeed proven problematic. See loadMoreGives
|
|
||||||
// We should display it immediately and then get the plan later.
|
|
||||||
const fulfillsPlan = await getPlanFromCache(
|
|
||||||
record.fulfillsPlanHandleId,
|
|
||||||
this.axios,
|
|
||||||
this.apiServer,
|
|
||||||
this.activeDid,
|
|
||||||
);
|
|
||||||
|
|
||||||
// check if the record should be filtered out
|
|
||||||
let anyMatch = false;
|
|
||||||
if (this.isFeedFilteredByVisible && containsNonHiddenDid(record)) {
|
|
||||||
// has a visible DID so it's a keeper
|
|
||||||
anyMatch = true;
|
|
||||||
}
|
|
||||||
if (!anyMatch && this.isFeedFilteredByNearby) {
|
|
||||||
// check if the associated project has a location inside user's search box
|
|
||||||
if (record.fulfillsPlanHandleId) {
|
|
||||||
if (fulfillsPlan?.locLat && fulfillsPlan?.locLon) {
|
|
||||||
if (
|
|
||||||
this.latLongInAnySearchBox(
|
|
||||||
fulfillsPlan.locLat,
|
|
||||||
fulfillsPlan.locLon,
|
|
||||||
)
|
|
||||||
) {
|
|
||||||
anyMatch = true;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if (this.isAnyFeedFilterOn && !anyMatch) {
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
|
|
||||||
// checking for arrays due to legacy data
|
|
||||||
const provider = Array.isArray(claim.provider)
|
|
||||||
? claim.provider[0]
|
|
||||||
: claim.provider;
|
|
||||||
const providedByPlan = await getPlanFromCache(
|
|
||||||
provider?.identifier as string,
|
|
||||||
this.axios,
|
|
||||||
this.apiServer,
|
|
||||||
this.activeDid,
|
|
||||||
);
|
|
||||||
|
|
||||||
const newRecord: GiveRecordWithContactInfo = {
|
|
||||||
...record,
|
|
||||||
jwtId: record.jwtId,
|
|
||||||
giver: didInfoForContact(
|
|
||||||
giverDid,
|
|
||||||
this.activeDid,
|
|
||||||
contactForDid(giverDid, this.allContacts),
|
|
||||||
this.allMyDids,
|
|
||||||
),
|
|
||||||
image: claim.image,
|
|
||||||
issuer: didInfoForContact(
|
|
||||||
record.issuerDid,
|
|
||||||
this.activeDid,
|
|
||||||
contactForDid(record.issuerDid, this.allContacts),
|
|
||||||
this.allMyDids,
|
|
||||||
),
|
|
||||||
providerPlanHandleId: provider?.identifier as string,
|
|
||||||
providerPlanName: providedByPlan?.name as string,
|
|
||||||
recipientProjectName: fulfillsPlan?.name as string,
|
|
||||||
receiver: didInfoForContact(
|
|
||||||
recipientDid,
|
|
||||||
this.activeDid,
|
|
||||||
contactForDid(recipientDid, this.allContacts),
|
|
||||||
this.allMyDids,
|
|
||||||
),
|
|
||||||
};
|
|
||||||
this.feedData.push(newRecord);
|
|
||||||
}
|
|
||||||
this.feedPreviousOldestId =
|
|
||||||
results.data[results.data.length - 1].jwtId;
|
|
||||||
// The following update is only done on the first load.
|
|
||||||
if (
|
|
||||||
this.feedLastViewedClaimId == null ||
|
|
||||||
this.feedLastViewedClaimId < results.data[0].jwtId
|
|
||||||
) {
|
|
||||||
await db.open();
|
|
||||||
await db.settings.update(MASTER_SETTINGS_KEY, {
|
|
||||||
lastViewedClaimId: results.data[0].jwtId,
|
|
||||||
});
|
|
||||||
}
|
|
||||||
}
|
|
||||||
})
|
|
||||||
.catch((e) => {
|
|
||||||
logger.error("Error with feed load:", e);
|
|
||||||
this.$notify(
|
|
||||||
{
|
|
||||||
group: "alert",
|
|
||||||
type: "danger",
|
|
||||||
title: "Feed Error",
|
|
||||||
text: e.userMessage || "There was an error retrieving feed data.",
|
|
||||||
},
|
|
||||||
-1,
|
|
||||||
);
|
|
||||||
});
|
|
||||||
if (this.feedData.length === 0 && !endOfResults) {
|
if (this.feedData.length === 0 && !endOfResults) {
|
||||||
// repeat until there's at least some data
|
|
||||||
await this.updateAllFeed();
|
await this.updateAllFeed();
|
||||||
}
|
}
|
||||||
|
|
||||||
this.isFeedLoading = false;
|
this.isFeedLoading = false;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Processes feed results and adds them to feedData
|
||||||
|
*/
|
||||||
|
private async processFeedResults(records: GiveSummaryRecord[]) {
|
||||||
|
for (const record of records) {
|
||||||
|
const processedRecord = await this.processRecord(record);
|
||||||
|
if (processedRecord) {
|
||||||
|
this.feedData.push(processedRecord);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
this.feedPreviousOldestId = records[records.length - 1].jwtId;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Processes a single record and returns it if it passes filters
|
||||||
|
*/
|
||||||
|
private async processRecord(record: GiveSummaryRecord): Promise<GiveRecordWithContactInfo | null> {
|
||||||
|
const claim = this.extractClaim(record);
|
||||||
|
const giverDid = this.extractGiverDid(claim);
|
||||||
|
const recipientDid = this.extractRecipientDid(claim);
|
||||||
|
|
||||||
|
const fulfillsPlan = await this.getFulfillsPlan(record);
|
||||||
|
if (!this.shouldIncludeRecord(record, fulfillsPlan)) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
const provider = this.extractProvider(claim);
|
||||||
|
const providedByPlan = await this.getProvidedByPlan(provider);
|
||||||
|
|
||||||
|
return this.createFeedRecord(record, claim, giverDid, recipientDid, provider, fulfillsPlan, providedByPlan);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Extracts claim from record, handling both direct and wrapped claims
|
||||||
|
*/
|
||||||
|
private extractClaim(record: GiveSummaryRecord) {
|
||||||
|
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
||||||
|
return (record.fullClaim as any).claim || record.fullClaim;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Extracts giver DID from claim
|
||||||
|
*/
|
||||||
|
private extractGiverDid(claim: any) {
|
||||||
|
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
||||||
|
return claim.agent?.identifier || (claim.agent as any)?.did;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Extracts recipient DID from claim
|
||||||
|
*/
|
||||||
|
private extractRecipientDid(claim: any) {
|
||||||
|
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
||||||
|
return claim.recipient?.identifier || (claim.recipient as any)?.did;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Gets fulfills plan from cache
|
||||||
|
*/
|
||||||
|
private async getFulfillsPlan(record: GiveSummaryRecord) {
|
||||||
|
return await getPlanFromCache(
|
||||||
|
record.fulfillsPlanHandleId,
|
||||||
|
this.axios,
|
||||||
|
this.apiServer,
|
||||||
|
this.activeDid,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Checks if record should be included based on filters
|
||||||
|
*/
|
||||||
|
private shouldIncludeRecord(record: GiveSummaryRecord, fulfillsPlan: any): boolean {
|
||||||
|
if (!this.isAnyFeedFilterOn) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
let anyMatch = false;
|
||||||
|
if (this.isFeedFilteredByVisible && containsNonHiddenDid(record)) {
|
||||||
|
anyMatch = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!anyMatch && this.isFeedFilteredByNearby && record.fulfillsPlanHandleId) {
|
||||||
|
if (fulfillsPlan?.locLat && fulfillsPlan?.locLon) {
|
||||||
|
anyMatch = this.latLongInAnySearchBox(fulfillsPlan.locLat, fulfillsPlan.locLon) ?? false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return anyMatch;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Extracts provider from claim
|
||||||
|
*/
|
||||||
|
private extractProvider(claim: any) {
|
||||||
|
return Array.isArray(claim.provider) ? claim.provider[0] : claim.provider;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Gets provided by plan from cache
|
||||||
|
*/
|
||||||
|
private async getProvidedByPlan(provider: any) {
|
||||||
|
return await getPlanFromCache(
|
||||||
|
provider?.identifier as string,
|
||||||
|
this.axios,
|
||||||
|
this.apiServer,
|
||||||
|
this.activeDid,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Creates a feed record with contact info
|
||||||
|
*/
|
||||||
|
private createFeedRecord(
|
||||||
|
record: GiveSummaryRecord,
|
||||||
|
claim: any,
|
||||||
|
giverDid: string,
|
||||||
|
recipientDid: string,
|
||||||
|
provider: any,
|
||||||
|
fulfillsPlan: any,
|
||||||
|
providedByPlan: any
|
||||||
|
): GiveRecordWithContactInfo {
|
||||||
|
return {
|
||||||
|
...record,
|
||||||
|
jwtId: record.jwtId,
|
||||||
|
fullClaim: record.fullClaim,
|
||||||
|
description: record.description || '',
|
||||||
|
handleId: record.handleId,
|
||||||
|
issuerDid: record.issuerDid,
|
||||||
|
fulfillsPlanHandleId: record.fulfillsPlanHandleId,
|
||||||
|
giver: didInfoForContact(
|
||||||
|
giverDid,
|
||||||
|
this.activeDid,
|
||||||
|
contactForDid(giverDid, this.allContacts),
|
||||||
|
this.allMyDids,
|
||||||
|
),
|
||||||
|
image: claim.image,
|
||||||
|
issuer: didInfoForContact(
|
||||||
|
record.issuerDid,
|
||||||
|
this.activeDid,
|
||||||
|
contactForDid(record.issuerDid, this.allContacts),
|
||||||
|
this.allMyDids,
|
||||||
|
),
|
||||||
|
providerPlanHandleId: provider?.identifier as string,
|
||||||
|
providerPlanName: providedByPlan?.name as string,
|
||||||
|
recipientProjectName: fulfillsPlan?.name as string,
|
||||||
|
receiver: didInfoForContact(
|
||||||
|
recipientDid,
|
||||||
|
this.activeDid,
|
||||||
|
contactForDid(recipientDid, this.allContacts),
|
||||||
|
this.allMyDids,
|
||||||
|
),
|
||||||
|
} as GiveRecordWithContactInfo;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Updates the last viewed claim ID in settings
|
||||||
|
*/
|
||||||
|
private async updateFeedLastViewedId(records: GiveSummaryRecord[]) {
|
||||||
|
if (
|
||||||
|
this.feedLastViewedClaimId == null ||
|
||||||
|
this.feedLastViewedClaimId < records[0].jwtId
|
||||||
|
) {
|
||||||
|
await db.open();
|
||||||
|
await db.settings.update(MASTER_SETTINGS_KEY, {
|
||||||
|
lastViewedClaimId: records[0].jwtId,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Handles feed error and shows notification
|
||||||
|
*/
|
||||||
|
private handleFeedError(e: any) {
|
||||||
|
logger.error("Error with feed load:", e);
|
||||||
|
this.$notify(
|
||||||
|
{
|
||||||
|
group: "alert",
|
||||||
|
type: "danger",
|
||||||
|
title: "Feed Error",
|
||||||
|
text: e.userMessage || "There was an error retrieving feed data.",
|
||||||
|
},
|
||||||
|
-1,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Retrieve claims in reverse chronological order
|
* Retrieve claims in reverse chronological order
|
||||||
*
|
*
|
||||||
|
|||||||
@@ -88,12 +88,8 @@ import { test, expect } from '@playwright/test';
|
|||||||
import { importUser, createUniqueStringsArray, createRandomNumbersArray } from './testUtils';
|
import { importUser, createUniqueStringsArray, createRandomNumbersArray } from './testUtils';
|
||||||
|
|
||||||
test('Record 9 new gifts', async ({ page }) => {
|
test('Record 9 new gifts', async ({ page }) => {
|
||||||
const giftCount = 9; // because 10 has taken us above 30 seconds
|
const giftCount = 9;
|
||||||
|
|
||||||
// Standard text
|
|
||||||
const standardTitle = 'Gift ';
|
const standardTitle = 'Gift ';
|
||||||
|
|
||||||
// Field value arrays
|
|
||||||
const finalTitles = [];
|
const finalTitles = [];
|
||||||
const finalNumbers = [];
|
const finalNumbers = [];
|
||||||
|
|
||||||
@@ -101,21 +97,19 @@ test('Record 9 new gifts', async ({ page }) => {
|
|||||||
const uniqueStrings = await createUniqueStringsArray(giftCount);
|
const uniqueStrings = await createUniqueStringsArray(giftCount);
|
||||||
const randomNumbers = await createRandomNumbersArray(giftCount);
|
const randomNumbers = await createRandomNumbersArray(giftCount);
|
||||||
|
|
||||||
// Populate array with titles
|
// Populate arrays
|
||||||
for (let i = 0; i < giftCount; i++) {
|
for (let i = 0; i < giftCount; i++) {
|
||||||
let loopTitle = standardTitle + uniqueStrings[i];
|
finalTitles.push(standardTitle + uniqueStrings[i]);
|
||||||
finalTitles.push(loopTitle);
|
finalNumbers.push(randomNumbers[i]);
|
||||||
let loopNumber = randomNumbers[i];
|
|
||||||
finalNumbers.push(loopNumber);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Import user 00
|
// Import user 00
|
||||||
await importUser(page, '00');
|
await importUser(page, '00');
|
||||||
|
|
||||||
// Record new gifts
|
// Record new gifts with optimized waiting
|
||||||
for (let i = 0; i < giftCount; i++) {
|
for (let i = 0; i < giftCount; i++) {
|
||||||
// Record something given
|
// Record gift
|
||||||
await page.goto('./');
|
await page.goto('./', { waitUntil: 'networkidle' });
|
||||||
if (i === 0) {
|
if (i === 0) {
|
||||||
await page.getByTestId('closeOnboardingAndFinish').click();
|
await page.getByTestId('closeOnboardingAndFinish').click();
|
||||||
}
|
}
|
||||||
@@ -123,11 +117,16 @@ test('Record 9 new gifts', async ({ page }) => {
|
|||||||
await page.getByPlaceholder('What was given').fill(finalTitles[i]);
|
await page.getByPlaceholder('What was given').fill(finalTitles[i]);
|
||||||
await page.getByRole('spinbutton').fill(finalNumbers[i].toString());
|
await page.getByRole('spinbutton').fill(finalNumbers[i].toString());
|
||||||
await page.getByRole('button', { name: 'Sign & Send' }).click();
|
await page.getByRole('button', { name: 'Sign & Send' }).click();
|
||||||
|
|
||||||
|
// Wait for success and dismiss
|
||||||
await expect(page.getByText('That gift was recorded.')).toBeVisible();
|
await expect(page.getByText('That gift was recorded.')).toBeVisible();
|
||||||
await page.locator('div[role="alert"] button > svg.fa-xmark').click(); // dismiss info alert
|
await page.locator('div[role="alert"] button > svg.fa-xmark').click();
|
||||||
|
|
||||||
// Refresh home view and check gift
|
// Verify gift in list with network idle wait
|
||||||
await page.goto('./');
|
await page.goto('./', { waitUntil: 'networkidle' });
|
||||||
await expect(page.locator('li').filter({ hasText: finalTitles[i] })).toBeVisible();
|
await expect(page.locator('ul#listLatestActivity li')
|
||||||
|
.filter({ hasText: finalTitles[i] })
|
||||||
|
.first())
|
||||||
|
.toBeVisible({ timeout: 10000 });
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
Reference in New Issue
Block a user