schwab-api
schwab-api copied to clipboard
Login fails when voice id is pending activation
If Voice ID is partially set up, the login page redirects to https://client.schwab.com/service/forms/VoiceBiometric instead of whichever page was selected:
The relevant HTML fragment for clicking "Cancel", which just skips voice activation enabling and going to the correct post-login page, is
<div class="background-Highlight">
<div id="divEnroll" class="section-msg">
<div id="divCmsReadyToEnroll">
<b>You're almost done!</b> Click Activate Voice ID and use your new voice ID the next time you call Schwab.
</div>
<div id="divButtonAreaEnroll" class="space-bttonarea">
<a role="button" id="btnCancel" class="button-secondary" href="#">
<span>Cancel</span>
</a>
<a role="button" id="btnActivateVoiceId" class="button-primary" href="/Service/Forms/VoiceBiometric/VoiceBiometricActivate">
<span>Activate Voice ID</span>
</a>
</div>
</div>
</div>
(Full page html is voice_activation_page_redacted.html.zip)
It would be nice if
https://github.com/itsjafer/schwab-api/blob/4ecc6242c58d93c6423bca2f1aa06e2260f25b31/schwab_api/authentication.py#L167
would also wait for service/forms/VoiceBiometric, and cancel if necessary.
For future reference, I debugged this with
try:
await self.page.frame(name=login_frame).press("[placeholder=\"Password\"]", "Enter")
await self.page.wait_for_url(re.compile(r"app/trade"), wait_until="domcontentloaded") # Making it more robust than specifying an exact url which may change.
except TimeoutError as e:
# Capture screenshot
await self.page.screenshot(path="debug_screenshot.png")
# Save page content
page_content = await self.page.content()
with open("debug_page_content.html", "w", encoding="utf-8") as f:
f.write(page_content)
print(f"Current URL: {self.page.url}")
raise RuntimeError(f"Login was not successful; please check username and password. Debug info saved to {os.path.abspath('debug_screenshot.png')} and {os.path.abspath('debug_page_content.html')}") from e