mirror of
https://github.com/LightZirconite/Microsoft-Rewards-Bot.git
synced 2026-01-09 17:06:15 +00:00
feature: Improve wait times with adaptive detection for better element responsiveness
This commit is contained in:
@@ -26,10 +26,13 @@ export default class BrowserFunc {
|
||||
* @returns true if suspended, false otherwise
|
||||
*/
|
||||
private async checkAccountSuspension(page: Page, iteration: number): Promise<boolean> {
|
||||
// Primary check: suspension header element
|
||||
const suspendedByHeader = await page.waitForSelector(SELECTORS.SUSPENDED_ACCOUNT, { state: 'visible', timeout: 500 })
|
||||
.then(() => true)
|
||||
.catch(() => false)
|
||||
// IMPROVED: Smart wait replaces fixed 500ms timeout with adaptive detection
|
||||
const headerResult = await waitForElementSmart(page, SELECTORS.SUSPENDED_ACCOUNT, {
|
||||
initialTimeoutMs: 500,
|
||||
extendedTimeoutMs: 500,
|
||||
state: 'visible'
|
||||
})
|
||||
const suspendedByHeader = headerResult.found
|
||||
|
||||
if (suspendedByHeader) {
|
||||
this.bot.log(this.bot.isMobile, 'GO-HOME', `Account suspension detected by header selector (iteration ${iteration})`, 'error')
|
||||
@@ -541,9 +544,20 @@ export default class BrowserFunc {
|
||||
|
||||
async waitForQuizRefresh(page: Page): Promise<boolean> {
|
||||
try {
|
||||
await page.waitForSelector(SELECTORS.QUIZ_CREDITS, { state: 'visible', timeout: TIMEOUTS.DASHBOARD_WAIT })
|
||||
await this.bot.utils.wait(TIMEOUTS.MEDIUM_LONG)
|
||||
// IMPROVED: Smart wait replaces fixed 10s timeout with adaptive 2s+5s detection
|
||||
const result = await waitForElementSmart(page, SELECTORS.QUIZ_CREDITS, {
|
||||
initialTimeoutMs: 2000,
|
||||
extendedTimeoutMs: TIMEOUTS.DASHBOARD_WAIT - 2000,
|
||||
state: 'visible',
|
||||
logFn: (msg) => this.bot.log(this.bot.isMobile, 'QUIZ-REFRESH', msg)
|
||||
})
|
||||
|
||||
if (!result.found) {
|
||||
this.bot.log(this.bot.isMobile, 'QUIZ-REFRESH', 'Quiz credits element not found', 'error')
|
||||
return false
|
||||
}
|
||||
|
||||
await this.bot.utils.wait(TIMEOUTS.MEDIUM_LONG)
|
||||
return true
|
||||
} catch (error) {
|
||||
this.bot.log(this.bot.isMobile, 'QUIZ-REFRESH', 'An error occurred:' + error, 'error')
|
||||
@@ -553,9 +567,19 @@ export default class BrowserFunc {
|
||||
|
||||
async checkQuizCompleted(page: Page): Promise<boolean> {
|
||||
try {
|
||||
await page.waitForSelector(SELECTORS.QUIZ_COMPLETE, { state: 'visible', timeout: TIMEOUTS.MEDIUM_LONG })
|
||||
await this.bot.utils.wait(TIMEOUTS.MEDIUM_LONG)
|
||||
// IMPROVED: Smart wait replaces fixed 2s timeout with adaptive detection
|
||||
const result = await waitForElementSmart(page, SELECTORS.QUIZ_COMPLETE, {
|
||||
initialTimeoutMs: 1000,
|
||||
extendedTimeoutMs: TIMEOUTS.MEDIUM_LONG,
|
||||
state: 'visible',
|
||||
logFn: (msg) => this.bot.log(this.bot.isMobile, 'QUIZ-COMPLETE', msg)
|
||||
})
|
||||
|
||||
if (!result.found) {
|
||||
return false
|
||||
}
|
||||
|
||||
await this.bot.utils.wait(TIMEOUTS.MEDIUM_LONG)
|
||||
return true
|
||||
} catch (error) {
|
||||
return false
|
||||
|
||||
@@ -4,6 +4,7 @@ import { TIMEOUTS } from '../constants'
|
||||
import { DashboardData, MorePromotion, PromotionalItem, PunchCard } from '../interface/DashboardData'
|
||||
|
||||
import { MicrosoftRewardsBot } from '../index'
|
||||
import { waitForElementSmart, waitForNetworkIdle } from '../util/browser/SmartWait'
|
||||
import { Retry } from '../util/core/Retry'
|
||||
import { AdaptiveThrottler } from '../util/notifications/AdaptiveThrottler'
|
||||
import { logError } from '../util/notifications/Logger'
|
||||
@@ -109,8 +110,11 @@ export class Workers {
|
||||
// Got to punch card index page in a new tab
|
||||
await page.goto(punchCard.parentPromotion.destinationUrl, { referer: this.bot.config.baseURL })
|
||||
|
||||
// Wait for new page to load, max 10 seconds, however try regardless in case of error
|
||||
await page.waitForLoadState('networkidle', { timeout: 5000 }).catch(logError('PUNCH-CARD', 'Network idle wait timeout (non-critical)', this.bot.isMobile))
|
||||
// IMPROVED: Smart wait replaces fixed 5s timeout with adaptive detection
|
||||
await waitForNetworkIdle(page, {
|
||||
timeoutMs: 5000,
|
||||
logFn: (msg) => this.bot.log(this.bot.isMobile, 'PUNCH-CARD', msg)
|
||||
}).catch(logError('PUNCH-CARD', 'Network idle wait timeout (non-critical)', this.bot.isMobile))
|
||||
|
||||
await this.solveActivities(page, activitiesUncompleted, punchCard)
|
||||
|
||||
@@ -232,7 +236,11 @@ export class Workers {
|
||||
}
|
||||
|
||||
private async prepareActivityPage(page: Page, selector: string, throttle: AdaptiveThrottler): Promise<void> {
|
||||
await page.waitForLoadState('networkidle', { timeout: TIMEOUTS.DASHBOARD_WAIT }).catch(logError('WORKERS', 'Network idle wait failed', this.bot.isMobile))
|
||||
// IMPROVED: Smart wait replaces fixed 10s timeout with adaptive detection
|
||||
await waitForNetworkIdle(page, {
|
||||
timeoutMs: TIMEOUTS.DASHBOARD_WAIT,
|
||||
logFn: (msg) => this.bot.log(this.bot.isMobile, 'WORKERS', msg)
|
||||
}).catch(logError('WORKERS', 'Network idle wait failed', this.bot.isMobile))
|
||||
await this.bot.browser.utils.humanizePage(page)
|
||||
await this.applyThrottle(throttle, ACTIVITY_DELAYS.ACTIVITY_SPACING_MIN, ACTIVITY_DELAYS.ACTIVITY_SPACING_MAX)
|
||||
}
|
||||
@@ -240,12 +248,17 @@ export class Workers {
|
||||
private async executeActivity(page: Page, activity: PromotionalItem | MorePromotion, selector: string, throttle: AdaptiveThrottler, retry: Retry): Promise<void> {
|
||||
this.bot.log(this.bot.isMobile, 'ACTIVITY', `Found activity type: "${this.bot.activities.getTypeLabel(activity)}" title: "${activity.title}"`)
|
||||
|
||||
// Check if element exists before clicking (avoid 30s timeout)
|
||||
try {
|
||||
await page.waitForSelector(selector, { timeout: TIMEOUTS.NETWORK_IDLE })
|
||||
} catch (error) {
|
||||
// IMPROVED: Smart wait replaces fixed 5s timeout with adaptive 2s+5s detection
|
||||
const elementResult = await waitForElementSmart(page, selector, {
|
||||
initialTimeoutMs: 2000,
|
||||
extendedTimeoutMs: TIMEOUTS.NETWORK_IDLE,
|
||||
state: 'attached',
|
||||
logFn: (msg) => this.bot.log(this.bot.isMobile, 'ACTIVITY', msg)
|
||||
})
|
||||
|
||||
if (!elementResult.found) {
|
||||
this.bot.log(this.bot.isMobile, 'ACTIVITY', `Activity selector not found (might be completed or unavailable): ${selector}`, 'warn')
|
||||
return // Skip this activity gracefully instead of waiting 30s
|
||||
return // Skip this activity gracefully
|
||||
}
|
||||
|
||||
// Click with timeout to prevent indefinite hangs
|
||||
|
||||
@@ -1,7 +1,8 @@
|
||||
import { Page } from 'rebrowser-playwright'
|
||||
|
||||
import { Workers } from '../Workers'
|
||||
import { RETRY_LIMITS, TIMEOUTS } from '../../constants'
|
||||
import { waitForElementSmart } from '../../util/browser/SmartWait'
|
||||
import { Workers } from '../Workers'
|
||||
|
||||
|
||||
export class ABC extends Workers {
|
||||
@@ -14,18 +15,51 @@ export class ABC extends Workers {
|
||||
|
||||
let i
|
||||
for (i = 0; i < RETRY_LIMITS.ABC_MAX && !$('span.rw_icon').length; i++) {
|
||||
await page.waitForSelector('.wk_OptionClickClass', { state: 'visible', timeout: TIMEOUTS.DASHBOARD_WAIT })
|
||||
// IMPROVED: Smart wait replaces fixed 10s timeout with adaptive 2s+5s detection
|
||||
const optionsResult = await waitForElementSmart(page, '.wk_OptionClickClass', {
|
||||
initialTimeoutMs: 2000,
|
||||
extendedTimeoutMs: TIMEOUTS.DASHBOARD_WAIT - 2000,
|
||||
state: 'visible',
|
||||
logFn: (msg) => this.bot.log(this.bot.isMobile, 'ABC', msg)
|
||||
})
|
||||
|
||||
if (!optionsResult.found) {
|
||||
this.bot.log(this.bot.isMobile, 'ABC', 'Options not found', 'warn')
|
||||
break
|
||||
}
|
||||
|
||||
const answers = $('.wk_OptionClickClass')
|
||||
const answer = answers[this.bot.utils.randomNumber(0, 2)]?.attribs['id']
|
||||
|
||||
await page.waitForSelector(`#${answer}`, { state: 'visible', timeout: TIMEOUTS.DASHBOARD_WAIT })
|
||||
// IMPROVED: Smart wait for specific answer
|
||||
const answerResult = await waitForElementSmart(page, `#${answer}`, {
|
||||
initialTimeoutMs: 1000,
|
||||
extendedTimeoutMs: TIMEOUTS.DASHBOARD_WAIT - 1000,
|
||||
state: 'visible'
|
||||
})
|
||||
|
||||
if (!answerResult.found) {
|
||||
this.bot.log(this.bot.isMobile, 'ABC', `Answer ${answer} not found`, 'warn')
|
||||
break
|
||||
}
|
||||
|
||||
await this.bot.utils.wait(TIMEOUTS.MEDIUM_LONG)
|
||||
await page.click(`#${answer}`) // Click answer
|
||||
|
||||
await this.bot.utils.wait(TIMEOUTS.LONG + 1000)
|
||||
await page.waitForSelector('div.wk_button', { state: 'visible', timeout: TIMEOUTS.DASHBOARD_WAIT })
|
||||
|
||||
// IMPROVED: Smart wait for next button
|
||||
const buttonResult = await waitForElementSmart(page, 'div.wk_button', {
|
||||
initialTimeoutMs: 2000,
|
||||
extendedTimeoutMs: TIMEOUTS.DASHBOARD_WAIT - 2000,
|
||||
state: 'visible'
|
||||
})
|
||||
|
||||
if (!buttonResult.found) {
|
||||
this.bot.log(this.bot.isMobile, 'ABC', 'Next button not found', 'warn')
|
||||
break
|
||||
}
|
||||
|
||||
await page.click('div.wk_button') // Click next question button
|
||||
|
||||
page = await this.bot.browser.utils.getLatestTab(page)
|
||||
|
||||
@@ -1,7 +1,8 @@
|
||||
import { Page } from 'rebrowser-playwright'
|
||||
|
||||
import { Workers } from '../Workers'
|
||||
import { TIMEOUTS } from '../../constants'
|
||||
import { waitForElementSmart } from '../../util/browser/SmartWait'
|
||||
import { Workers } from '../Workers'
|
||||
|
||||
|
||||
export class Poll extends Workers {
|
||||
@@ -12,11 +13,21 @@ export class Poll extends Workers {
|
||||
try {
|
||||
const buttonId = `#btoption${Math.floor(this.bot.utils.randomNumber(0, 1))}`
|
||||
|
||||
await page.waitForSelector(buttonId, { state: 'visible', timeout: TIMEOUTS.DASHBOARD_WAIT }).catch((e) => {
|
||||
this.bot.log(this.bot.isMobile, 'POLL', `Could not find poll button: ${e}`, 'warn')
|
||||
// IMPROVED: Smart wait replaces fixed 10s timeout with adaptive 2s+5s detection
|
||||
const buttonResult = await waitForElementSmart(page, buttonId, {
|
||||
initialTimeoutMs: 2000,
|
||||
extendedTimeoutMs: TIMEOUTS.DASHBOARD_WAIT - 2000,
|
||||
state: 'visible',
|
||||
logFn: (msg) => this.bot.log(this.bot.isMobile, 'POLL', msg)
|
||||
})
|
||||
await this.bot.utils.wait(TIMEOUTS.MEDIUM_LONG)
|
||||
|
||||
if (!buttonResult.found) {
|
||||
this.bot.log(this.bot.isMobile, 'POLL', `Could not find poll button: ${buttonId}`, 'warn')
|
||||
await page.close()
|
||||
return
|
||||
}
|
||||
|
||||
await this.bot.utils.wait(TIMEOUTS.MEDIUM_LONG)
|
||||
await page.click(buttonId)
|
||||
|
||||
await this.bot.utils.wait(TIMEOUTS.LONG + 1000)
|
||||
|
||||
@@ -1,7 +1,8 @@
|
||||
import { Page } from 'rebrowser-playwright'
|
||||
|
||||
import { DELAYS, RETRY_LIMITS, TIMEOUTS } from '../../constants'
|
||||
import { waitForElementSmart } from '../../util/browser/SmartWait'
|
||||
import { Workers } from '../Workers'
|
||||
import { RETRY_LIMITS, TIMEOUTS, DELAYS } from '../../constants'
|
||||
|
||||
|
||||
export class Quiz extends Workers {
|
||||
@@ -10,9 +11,15 @@ export class Quiz extends Workers {
|
||||
this.bot.log(this.bot.isMobile, 'QUIZ', 'Trying to complete quiz')
|
||||
|
||||
try {
|
||||
// Check if the quiz has been started or not
|
||||
const quizNotStarted = await page.waitForSelector('#rqStartQuiz', { state: 'visible', timeout: TIMEOUTS.MEDIUM_LONG }).then(() => true).catch(() => false)
|
||||
if (quizNotStarted) {
|
||||
// IMPROVED: Smart wait replaces fixed 2s timeout with adaptive detection
|
||||
const startQuizResult = await waitForElementSmart(page, '#rqStartQuiz', {
|
||||
initialTimeoutMs: 1000,
|
||||
extendedTimeoutMs: TIMEOUTS.MEDIUM_LONG,
|
||||
state: 'visible',
|
||||
logFn: (msg) => this.bot.log(this.bot.isMobile, 'QUIZ', msg)
|
||||
})
|
||||
|
||||
if (startQuizResult.found) {
|
||||
await page.click('#rqStartQuiz')
|
||||
} else {
|
||||
this.bot.log(this.bot.isMobile, 'QUIZ', 'Quiz has already been started, trying to finish it')
|
||||
@@ -21,10 +28,16 @@ export class Quiz extends Workers {
|
||||
await this.bot.utils.wait(TIMEOUTS.MEDIUM_LONG)
|
||||
|
||||
let quizData = await this.bot.browser.func.getQuizData(page)
|
||||
|
||||
// Verify quiz is actually loaded before proceeding
|
||||
const firstOptionExists = await page.waitForSelector('#rqAnswerOption0', { state: 'attached', timeout: TIMEOUTS.VERY_LONG }).then(() => true).catch(() => false)
|
||||
if (!firstOptionExists) {
|
||||
|
||||
// IMPROVED: Smart wait replaces fixed 5s timeout with adaptive detection
|
||||
const firstOptionResult = await waitForElementSmart(page, '#rqAnswerOption0', {
|
||||
initialTimeoutMs: 2000,
|
||||
extendedTimeoutMs: TIMEOUTS.VERY_LONG,
|
||||
state: 'attached',
|
||||
logFn: (msg) => this.bot.log(this.bot.isMobile, 'QUIZ', msg)
|
||||
})
|
||||
|
||||
if (!firstOptionResult.found) {
|
||||
this.bot.log(this.bot.isMobile, 'QUIZ', 'Quiz options not found - page may not have loaded correctly. Skipping.', 'warn')
|
||||
await page.close()
|
||||
return
|
||||
@@ -38,20 +51,27 @@ export class Quiz extends Workers {
|
||||
const answers: string[] = []
|
||||
|
||||
for (let i = 0; i < quizData.numberOfOptions; i++) {
|
||||
const answerSelector = await page.waitForSelector(`#rqAnswerOption${i}`, { state: 'visible', timeout: TIMEOUTS.DASHBOARD_WAIT }).catch(() => null)
|
||||
|
||||
if (!answerSelector) {
|
||||
// IMPROVED: Smart wait replaces fixed 10s timeout with adaptive 2s+5s detection
|
||||
const optionResult = await waitForElementSmart(page, `#rqAnswerOption${i}`, {
|
||||
initialTimeoutMs: 2000,
|
||||
extendedTimeoutMs: TIMEOUTS.DASHBOARD_WAIT - 2000,
|
||||
state: 'visible'
|
||||
})
|
||||
|
||||
if (!optionResult.found || !optionResult.element) {
|
||||
this.bot.log(this.bot.isMobile, 'QUIZ', `Option ${i} not found - quiz structure may have changed. Skipping remaining options.`, 'warn')
|
||||
break
|
||||
}
|
||||
|
||||
|
||||
const answerSelector = optionResult.element
|
||||
|
||||
const answerAttribute = await answerSelector?.evaluate((el: Element) => el.getAttribute('iscorrectoption'))
|
||||
|
||||
if (answerAttribute && answerAttribute.toLowerCase() === 'true') {
|
||||
answers.push(`#rqAnswerOption${i}`)
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
// If no correct answers found, skip this question
|
||||
if (answers.length === 0) {
|
||||
this.bot.log(this.bot.isMobile, 'QUIZ', 'No correct answers found for 8-option quiz. Skipping.', 'warn')
|
||||
@@ -61,7 +81,17 @@ export class Quiz extends Workers {
|
||||
|
||||
// Click the answers
|
||||
for (const answer of answers) {
|
||||
await page.waitForSelector(answer, { state: 'visible', timeout: DELAYS.QUIZ_ANSWER_WAIT })
|
||||
// IMPROVED: Smart wait replaces fixed 2s timeout with adaptive detection
|
||||
const answerResult = await waitForElementSmart(page, answer, {
|
||||
initialTimeoutMs: 1000,
|
||||
extendedTimeoutMs: DELAYS.QUIZ_ANSWER_WAIT,
|
||||
state: 'visible'
|
||||
})
|
||||
|
||||
if (!answerResult.found) {
|
||||
this.bot.log(this.bot.isMobile, 'QUIZ', `Answer element ${answer} not found`, 'warn')
|
||||
continue
|
||||
}
|
||||
|
||||
// Click the answer on page
|
||||
await page.click(answer)
|
||||
@@ -78,18 +108,23 @@ export class Quiz extends Workers {
|
||||
} else if ([2, 3, 4].includes(quizData.numberOfOptions)) {
|
||||
quizData = await this.bot.browser.func.getQuizData(page) // Refresh Quiz Data
|
||||
const correctOption = quizData.correctAnswer
|
||||
|
||||
|
||||
let answerClicked = false
|
||||
|
||||
for (let i = 0; i < quizData.numberOfOptions; i++) {
|
||||
// IMPROVED: Smart wait replaces fixed 10s timeout with adaptive detection
|
||||
const optionResult = await waitForElementSmart(page, `#rqAnswerOption${i}`, {
|
||||
initialTimeoutMs: 2000,
|
||||
extendedTimeoutMs: RETRY_LIMITS.QUIZ_ANSWER_TIMEOUT - 2000,
|
||||
state: 'visible'
|
||||
})
|
||||
|
||||
const answerSelector = await page.waitForSelector(`#rqAnswerOption${i}`, { state: 'visible', timeout: RETRY_LIMITS.QUIZ_ANSWER_TIMEOUT }).catch(() => null)
|
||||
|
||||
if (!answerSelector) {
|
||||
if (!optionResult.found || !optionResult.element) {
|
||||
this.bot.log(this.bot.isMobile, 'QUIZ', `Option ${i} not found for ${quizData.numberOfOptions}-option quiz. Skipping.`, 'warn')
|
||||
continue
|
||||
}
|
||||
|
||||
|
||||
const answerSelector = optionResult.element
|
||||
const dataOption = await answerSelector?.evaluate((el: Element) => el.getAttribute('data-option'))
|
||||
|
||||
if (dataOption === correctOption) {
|
||||
@@ -106,13 +141,13 @@ export class Quiz extends Workers {
|
||||
break
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
if (!answerClicked) {
|
||||
this.bot.log(this.bot.isMobile, 'QUIZ', `Could not find correct answer for ${quizData.numberOfOptions}-option quiz. Skipping.`, 'warn')
|
||||
await page.close()
|
||||
return
|
||||
}
|
||||
|
||||
|
||||
await this.bot.utils.wait(DELAYS.QUIZ_ANSWER_WAIT)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -6,6 +6,7 @@ import { Workers } from '../Workers'
|
||||
import { AxiosRequestConfig } from 'axios'
|
||||
import { Counters, DashboardData } from '../../interface/DashboardData'
|
||||
import { GoogleSearch } from '../../interface/Search'
|
||||
import { waitForElementSmart } from '../../util/browser/SmartWait'
|
||||
|
||||
type GoogleTrendsResponse = [
|
||||
string,
|
||||
@@ -69,7 +70,7 @@ export class Search extends Workers {
|
||||
}
|
||||
|
||||
googleSearchQueries = this.bot.utils.shuffleArray(googleSearchQueries)
|
||||
|
||||
|
||||
// Combined deduplication: exact + semantic in single pass for performance
|
||||
if (this.bot.config.searchSettings.semanticDedup !== false) {
|
||||
const threshold = this.bot.config.searchSettings.semanticDedupThreshold ?? 0.65
|
||||
@@ -160,7 +161,7 @@ export class Search extends Workers {
|
||||
const filteredRelated = this.bot.config.searchSettings.semanticDedup !== false
|
||||
? this.semanticDedupStrings(relatedTerms, this.bot.config.searchSettings.semanticDedupThreshold ?? 0.65)
|
||||
: relatedTerms
|
||||
|
||||
|
||||
// Search for the first 2 related terms
|
||||
for (const term of filteredRelated.slice(1, 3)) {
|
||||
this.bot.log(this.bot.isMobile, 'SEARCH-BING-EXTRA', `${missingPoints} Points Remaining | Query: ${term}`)
|
||||
@@ -314,7 +315,7 @@ export class Search extends Workers {
|
||||
|
||||
const mappedTrendsData = trendsData.map(query => [query[0], query[9]!.slice(1)])
|
||||
this.bot.log(this.bot.isMobile, 'SEARCH-GOOGLE-TRENDS', `Found ${mappedTrendsData.length} search queries for ${geoLocale}`)
|
||||
|
||||
|
||||
if (mappedTrendsData.length < 30 && geoLocale.toUpperCase() !== 'US') {
|
||||
this.bot.log(this.bot.isMobile, 'SEARCH-GOOGLE-TRENDS', `Insufficient search queries (${mappedTrendsData.length} < 30), falling back to US`, 'warn')
|
||||
return this.getGoogleTrends()
|
||||
@@ -388,7 +389,7 @@ export class Search extends Workers {
|
||||
private async clickRandomLink(page: Page) {
|
||||
try {
|
||||
// Silent catch justified: Click is best-effort humanization, failure is acceptable
|
||||
await page.click('#b_results .b_algo h2', { timeout: 2000 }).catch(() => {})
|
||||
await page.click('#b_results .b_algo h2', { timeout: 2000 }).catch(() => { })
|
||||
|
||||
// Only used if the browser is not the edge browser (continue on Edge popup)
|
||||
await this.closeContinuePopup(page)
|
||||
@@ -468,11 +469,15 @@ export class Search extends Workers {
|
||||
|
||||
private async closeContinuePopup(page: Page) {
|
||||
try {
|
||||
await page.waitForSelector('#sacs_close', { timeout: 1000 })
|
||||
const continueButton = await page.$('#sacs_close')
|
||||
// IMPROVED: Smart wait replaces fixed 1s timeout with adaptive detection
|
||||
const buttonResult = await waitForElementSmart(page, '#sacs_close', {
|
||||
initialTimeoutMs: 500,
|
||||
extendedTimeoutMs: 1000,
|
||||
state: 'attached'
|
||||
})
|
||||
|
||||
if (continueButton) {
|
||||
await continueButton.click()
|
||||
if (buttonResult.found && buttonResult.element) {
|
||||
await buttonResult.element.click()
|
||||
}
|
||||
} catch (error) {
|
||||
// Continue if element is not found or other error occurs
|
||||
@@ -498,24 +503,24 @@ export class Search extends Workers {
|
||||
private combinedDeduplication(queries: GoogleSearch[], threshold = 0.65): GoogleSearch[] {
|
||||
const result: GoogleSearch[] = []
|
||||
const seen = new Set<string>() // Track exact duplicates (case-insensitive)
|
||||
|
||||
|
||||
for (const query of queries) {
|
||||
const lower = query.topic.toLowerCase()
|
||||
|
||||
|
||||
// Check exact duplicate first (faster)
|
||||
if (seen.has(lower)) continue
|
||||
|
||||
|
||||
// Check semantic similarity with existing results
|
||||
const isSimilar = result.some(existing =>
|
||||
const isSimilar = result.some(existing =>
|
||||
this.jaccardSimilarity(query.topic, existing.topic) > threshold
|
||||
)
|
||||
|
||||
|
||||
if (!isSimilar) {
|
||||
result.push(query)
|
||||
seen.add(lower)
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
return result
|
||||
}
|
||||
|
||||
@@ -525,7 +530,7 @@ export class Search extends Workers {
|
||||
private semanticDedupStrings(terms: string[], threshold = 0.65): string[] {
|
||||
const result: string[] = []
|
||||
for (const term of terms) {
|
||||
const isSimilar = result.some(existing =>
|
||||
const isSimilar = result.some(existing =>
|
||||
this.jaccardSimilarity(term, existing) > threshold
|
||||
)
|
||||
if (!isSimilar) {
|
||||
|
||||
@@ -1,7 +1,8 @@
|
||||
import { Page } from 'rebrowser-playwright'
|
||||
|
||||
import { Workers } from '../Workers'
|
||||
import { DELAYS } from '../../constants'
|
||||
import { waitForElementSmart } from '../../util/browser/SmartWait'
|
||||
import { Workers } from '../Workers'
|
||||
|
||||
|
||||
export class ThisOrThat extends Workers {
|
||||
@@ -11,9 +12,15 @@ export class ThisOrThat extends Workers {
|
||||
|
||||
|
||||
try {
|
||||
// Check if the quiz has been started or not
|
||||
const quizNotStarted = await page.waitForSelector('#rqStartQuiz', { state: 'visible', timeout: DELAYS.THIS_OR_THAT_START }).then(() => true).catch(() => false)
|
||||
if (quizNotStarted) {
|
||||
// IMPROVED: Smart wait replaces fixed 2s timeout with adaptive detection
|
||||
const startQuizResult = await waitForElementSmart(page, '#rqStartQuiz', {
|
||||
initialTimeoutMs: 1000,
|
||||
extendedTimeoutMs: DELAYS.THIS_OR_THAT_START,
|
||||
state: 'visible',
|
||||
logFn: (msg) => this.bot.log(this.bot.isMobile, 'THIS-OR-THAT', msg)
|
||||
})
|
||||
|
||||
if (startQuizResult.found) {
|
||||
await page.click('#rqStartQuiz')
|
||||
} else {
|
||||
this.bot.log(this.bot.isMobile, 'THIS-OR-THAT', 'ThisOrThat has already been started, trying to finish it')
|
||||
|
||||
@@ -1,10 +1,54 @@
|
||||
/**
|
||||
* Smart waiting utilities for browser automation
|
||||
* Replaces fixed timeouts with intelligent page readiness detection
|
||||
*/
|
||||
|
||||
import { Locator, Page } from 'rebrowser-playwright';
|
||||
|
||||
/**
|
||||
* Wait for network idle state specifically
|
||||
* Optimized for post-navigation or post-action network settling
|
||||
*
|
||||
* @param page Playwright page instance
|
||||
* @param options Configuration options
|
||||
* @returns Result with completion status and timing
|
||||
*/
|
||||
export async function waitForNetworkIdle(
|
||||
page: Page,
|
||||
options: {
|
||||
timeoutMs?: number
|
||||
logFn?: (msg: string) => void
|
||||
} = {}
|
||||
): Promise<{ idle: boolean; timeMs: number }> {
|
||||
const startTime = Date.now()
|
||||
const timeoutMs = options.timeoutMs ?? 5000
|
||||
const logFn = options.logFn ?? (() => { })
|
||||
|
||||
try {
|
||||
// Quick check: is network already idle?
|
||||
const hasActivity = await page.evaluate(() => {
|
||||
return (performance.getEntriesByType('resource') as PerformanceResourceTiming[])
|
||||
.some(r => r.responseEnd === 0)
|
||||
}).catch(() => false)
|
||||
|
||||
if (!hasActivity) {
|
||||
const elapsed = Date.now() - startTime
|
||||
logFn(`✓ Network already idle (${elapsed}ms)`)
|
||||
return { idle: true, timeMs: elapsed }
|
||||
}
|
||||
|
||||
// Wait for network to settle
|
||||
await page.waitForLoadState('networkidle', { timeout: timeoutMs }).catch(() => {
|
||||
logFn(`Network idle timeout (${timeoutMs}ms) - continuing anyway`)
|
||||
})
|
||||
|
||||
const elapsed = Date.now() - startTime
|
||||
logFn(`✓ Network idle after ${elapsed}ms`)
|
||||
return { idle: true, timeMs: elapsed }
|
||||
|
||||
} catch (error) {
|
||||
const elapsed = Date.now() - startTime
|
||||
const errorMsg = error instanceof Error ? error.message : String(error)
|
||||
logFn(`⚠ Network idle check failed after ${elapsed}ms: ${errorMsg}`)
|
||||
return { idle: false, timeMs: elapsed }
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Wait for page to be truly ready (network idle + DOM ready)
|
||||
* Much faster than waitForLoadState with fixed timeouts
|
||||
|
||||
Reference in New Issue
Block a user