fix(myTischtennis): refine CAPTCHA readiness checks and improve interaction flow

- Introduced a new mechanism to detect CAPTCHA readiness before form submission, enhancing login reliability.
- Adjusted timeout settings for CAPTCHA field checks to optimize performance during login attempts.
- Added diagnostic logging for better visibility into CAPTCHA state changes and interaction outcomes.
This commit is contained in:
Torsten Schulz (local)
2026-03-02 11:17:26 +01:00
parent e1e8b5f4a4
commit 6ab6319256

View File

@@ -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()) {