diff --git a/backend/clients/myTischtennisClient.js b/backend/clients/myTischtennisClient.js index 89baa595..509d51d5 100644 --- a/backend/clients/myTischtennisClient.js +++ b/backend/clients/myTischtennisClient.js @@ -401,6 +401,7 @@ class MyTischtennisClient { } const captchaHost = page.locator('private-captcha').first(); const hasCaptchaHost = (await captchaHost.count()) > 0; + let captchaReadyDetected = !hasCaptchaHost; if (hasCaptchaHost) { try { await page.waitForTimeout(1200); @@ -433,7 +434,31 @@ class MyTischtennisClient { }); console.log('[myTischtennisClient.playwright] evaluate interaction result:', interaction); - // Wait for a visual captcha state change in Shadow DOM (not only hidden fields). + // Wait until hidden captcha fields are populated by site scripts. + try { + await page.waitForFunction(() => { + const captchaField = document.querySelector('input[name="captcha"]'); + const clickedField = document.querySelector('input[name="captcha_clicked"]'); + const captchaValue = (captchaField && captchaField.value ? captchaField.value.trim() : ''); + const clickedValue = (clickedField && clickedField.value ? clickedField.value.toLowerCase() : ''); + return captchaValue.length > 80 && (clickedValue === 'true' || clickedValue === '1'); + }, { timeout: 20000 }); + const captchaState = await page.evaluate(() => { + const captchaField = document.querySelector('input[name="captcha"]'); + const clickedField = document.querySelector('input[name="captcha_clicked"]'); + return { + captchaLen: captchaField?.value?.length || 0, + captchaClicked: clickedField?.value || null + }; + }); + console.log('[myTischtennisClient.playwright] Captcha value ready:', captchaState); + captchaReadyDetected = true; + } catch (_waitErr) { + // Keep going; some flows still succeed without explicit hidden field update. + console.warn('[myTischtennisClient.playwright] Captcha value not ready in time'); + } + + // Optional diagnostic only: visual state change should never block submit. try { await page.waitForFunction((beforeState) => { const host = document.querySelector('private-captcha'); @@ -455,57 +480,11 @@ class MyTischtennisClient { || current.checkboxChecked !== !!beforeState?.checkboxChecked || current.checkboxAriaChecked !== (beforeState?.checkboxAriaChecked || ''); - const positiveSignals = [ - current.hostClass, - current.hostDataState, - current.checkboxClass, - String(current.checkboxAriaChecked) - ].join(' ').toLowerCase(); - - return visualChanged && ( - current.checkboxChecked - || current.checkboxAriaChecked === 'true' - || /success|solved|verified|checked|active|complete|done/.test(positiveSignals) - ); - }, captchaVisualStateBefore, { timeout: 30000 }); - - const captchaVisualStateAfter = await page.evaluate(() => { - const host = document.querySelector('private-captcha'); - const checkbox = host?.shadowRoot?.querySelector('#pc-checkbox'); - return { - hostClass: host?.className || null, - hostDataState: host?.getAttribute?.('data-state') || null, - checkboxClass: checkbox?.className || null, - checkboxChecked: !!checkbox?.checked, - checkboxAriaChecked: checkbox?.getAttribute?.('aria-checked') || null - }; - }); - console.log('[myTischtennisClient.playwright] Captcha visual state changed:', captchaVisualStateAfter); + return visualChanged; + }, captchaVisualStateBefore, { timeout: 1500 }); + console.log('[myTischtennisClient.playwright] Captcha visual state changed'); } catch (_visualWaitErr) { - console.warn('[myTischtennisClient.playwright] Captcha visual state did not change in time'); - } - - // Wait until hidden captcha fields are populated by site scripts. - try { - await page.waitForFunction(() => { - const captchaField = document.querySelector('input[name="captcha"]'); - const clickedField = document.querySelector('input[name="captcha_clicked"]'); - const captchaValue = (captchaField && captchaField.value ? captchaField.value.trim() : ''); - const clickedValue = (clickedField && clickedField.value ? clickedField.value.toLowerCase() : ''); - return captchaValue.length > 80 && (clickedValue === 'true' || clickedValue === '1'); - }, { timeout: 30000 }); - const captchaState = await page.evaluate(() => { - const captchaField = document.querySelector('input[name="captcha"]'); - const clickedField = document.querySelector('input[name="captcha_clicked"]'); - return { - captchaLen: captchaField?.value?.length || 0, - captchaClicked: clickedField?.value || null - }; - }); - console.log('[myTischtennisClient.playwright] Captcha value ready:', captchaState); - } catch (_waitErr) { - // Keep going; some flows still succeed without explicit hidden field update. - console.warn('[myTischtennisClient.playwright] Captcha value not ready in time'); + // no-op: widget often keeps "ready" class despite solved token } } catch (captchaError) { console.warn('[myTischtennisClient.playwright] Captcha interaction warning:', captchaError?.message || captchaError); @@ -529,6 +508,7 @@ class MyTischtennisClient { const clickedValue = (clickedField && clickedField.value ? clickedField.value.toLowerCase() : ''); return captchaValue.length > 80 && (clickedValue === 'true' || clickedValue === '1'); }); + captchaReadyDetected = captchaReadyDetected || isCaptchaReadyNow; if (!isCaptchaReadyNow) { try { @@ -538,7 +518,8 @@ class MyTischtennisClient { const captchaValue = (captchaField && captchaField.value ? captchaField.value.trim() : ''); const clickedValue = (clickedField && clickedField.value ? clickedField.value.toLowerCase() : ''); return captchaValue.length > 80 && (clickedValue === 'true' || clickedValue === '1'); - }, { timeout: 45000 }); + }, { timeout: 12000 }); + captchaReadyDetected = true; } catch (_captchaNotReadyErr) { return { success: false, @@ -548,6 +529,13 @@ class MyTischtennisClient { } } + // Human-like pause only after captcha was actually solved (2-6s). + if (captchaReadyDetected) { + const postCaptchaDelayMs = 2000 + Math.floor(Math.random() * 4001); + await page.waitForTimeout(postCaptchaDelayMs); + console.log('[myTischtennisClient.playwright] Waited after solved captcha:', postCaptchaDelayMs); + } + // Submit form const submitButton = page.locator('button[type="submit"], input[type="submit"]').first(); if (await submitButton.count()) {