Microsoft changed the converged sign-in page to pre-render the password
view, so input#i0118 is present and visible while the email view is still
on screen. wait_for_selector() for the password field therefore returned
immediately, the fill landed mid view-switch and was wiped, and the Sign in
click submitted an empty box ("Please enter your password."). Planner never
loaded, no Graph request fired, and the run died with "Could not capture
Bearer token" on every machine from 2026-07-31 onwards.
Login flow:
- _fill_verified(): wait for the field to be interactive, fill, and read the
value back before submitting; retry when it does not stick.
- _submit_password()/_submit_totp(): confirm the page actually advanced, and
raise a specific error naming Microsoft's message when a credential is
rejected instead of timing out silently.
- _skip_login_interrupt(): dismiss the post-sign-in interstitials, including
"Let's keep your account secure", which was also never handled. The old
"Stay signed in?" handler only matched input#idBtn_Back and Microsoft now
renders that control as a <button>.
Token capture:
- Listen at context level so popups are observed.
- _TokenStore falls back to any graph.microsoft.com Bearer if no Planner-scoped
call appears, and records every Graph URL seen for diagnosis.
- MSAL localStorage fallback when no Graph request is intercepted at all.
- On failure, dump a screenshot, the page HTML and the request summary next to
the run log so the next regression does not need a code change to diagnose.
- Login timeout 120s -> 180s, with one retry of the whole login.
Also: force UTF-8 on stdout/stderr (the error banner crashed with
UnicodeEncodeError on a cp1252 console, hiding the real error), and only
prompt "Press Enter to close" when stdin is a TTY so scheduled runs do not
fail with EOFError.
Build: onedrive_planner_http.spec was gitignored and missing from the repo, so
build_exe.bat could not run; it is now tracked. requirements.txt was missing
httpx, playwright and python-dotenv.
Verified: scripts/verify_login.py completes login, token capture and read-only
Graph access (63 plans discovered). 103 tests pass.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
OneDrive / Planner download-only automation
Automates the MISF VOB face-sheet download workflow: sign in to OneDrive, open Planner, search each facility plan, download attachment PDFs, and mark tasks In progress.
Reference: docs/MISF-VOB-Planner-Workflow.docx
This repository contains only OneDrive / Planner download automation. There is no Noridian Medicare, Medi-Cal, PDF extraction, or eligibility verification code.
Source requirements
| Doc step | Action |
|---|---|
| 1 | Open OneDrive URL, enter username / password, complete MFA (Google Authenticator 6-digit code) |
| 2–4 | Open Planner (direct URL in config; default: Assigned to me grid) |
| 5 | For each facility: search/select plan → open tasks → download attachment PDFs → set task status In progress (board card, or Task details → Status) |
Clarifications:
- Process 54 facilities by typing each name into Planner’s search box, one at a time.
- MFA via TOTP secret in
Input/input.xlsx(automated withpyotp).
Not in this repo (upload path from the MISF guide):
- Uploading completed work to OneDrive
- Marking tasks Completed after eligibility verification
flowchart TD
start[run_download_workflow] --> config[Read input.xlsx]
config --> chrome[start_chrome_debug + attach_browser]
chrome --> login[Microsoft login + TOTP]
login --> planner[Open Planner / In my plans]
planner --> loop{For each facility}
loop --> search[Search facility in Planner]
search --> dl[Download task attachments]
dl --> status[Mark tasks In progress]
status --> loop
loop --> summary[Log summary + Excel report]
Repository layout
| File | Role |
|---|---|
run_download.py |
Main entry point |
src/planner_download.py |
Orchestrator: config → Chrome → login → facility loop → summary |
src/onedrive_planner.py |
Selenium: Microsoft login, Planner, downloads, In-Process |
src/config.py |
read_app_config(), read_facility_list() |
src/totp_utils.py |
TOTP secret sanitization / validation |
scripts/validate_config.py |
Pre-flight config check (no browser) |
src/chrome.py |
Chrome debug startup, Selenium attach, proxy auth |
scripts/bootstrap_input_xlsx.py |
Create config template |
tests/test_download_config.py |
Config smoke tests |
.
├── README.md
├── requirements.txt
├── run_download.py
├── docs/
├── Input/
│ ├── input.xlsx.example
│ ├── input.xlsx # gitignored
│ └── PDFs/
├── Output/
├── scripts/
├── tests/
└── src/
├── planner_download.py
├── onedrive_planner.py
├── config.py
└── chrome.py
Configuration (Input/input.xlsx)
copy Input\input.xlsx.example Input\input.xlsx
copy .env.example .env
Fill credentials in either place:
Input/input.xlsx→ Sheet1, column B (OneDrive Username,OneDrive Password,Microsoft TOTP Secret), or.envin the project root (ONEDRIVE_USERNAME,ONEDRIVE_PASSWORD,MICROSOFT_TOTP_SECRET)
Check config before running:
python scripts\validate_config.py
Sheet Sheet1
| Key | Purpose |
|---|---|
OneDrive Username |
Microsoft login email |
OneDrive Password |
Password |
Microsoft TOTP Secret |
Base32 authenticator secret (pyotp) |
OneDrive URL |
OneDrive landing URL |
pdf_folder_path |
Download root (default: Input/PDFs) |
Proxy IP / Proxy Port / Proxy Username / Proxy Password |
Optional |
Sheet Facilities — put all 54 facility names here
Open Input/input.xlsx (not the .example file) and edit the Facilities sheet:
| Column | Purpose |
|---|---|
Facility Name |
One row per facility (54 rows). Use the exact text you type in Planner search. |
Example:
| Facility Name |
|---|
| Amaya |
| Brighton Place San Diego |
| … (52 more rows) |
The bot reads this sheet via read_facility_list() in src/config.py. For a first test, keep only 2 rows and run python run_download.py --limit 2.
Production .exe (double-click)
Build a Windows folder you can copy to any PC:
powershell -ExecutionPolicy Bypass -File scripts\build_production_exe.ps1
Output at project root: MISF_VOB_Download.exe and _internal\ (same folder as Input/, Output/, run_download.py).
Deploy: zip the project folder (omit .venv, build, src if you only ship the exe build). On the target machine:
- Unzip anywhere
- Copy
Input\input.xlsx.example→Input\input.xlsxand configure credentials + facilities - Double-click
MISF_VOB_Download.exe
Config, Input/, and Output/ are read from the project root (folder containing the .exe). See README_PRODUCTION.txt after build.
Quick start
python -m venv .venv
.\.venv\Scripts\Activate.ps1
pip install -r requirements.txt
copy Input\input.xlsx.example Input\input.xlsx
# Edit Input\input.xlsx
python run_download.py --limit 2 # dry-run (first N facilities)
python run_download.py --facility "Amaya" # single facility
python run_download.py --resume # continue after last checkpoint
python run_download.py --keep-browser # leave browser open when done
python run_download.py # full run
Outputs:
Input/PDFs/<FacilityName>/*.pdfOutput/<date>/Summary/Download_Summary_<date>.xlsx(includes run timestamp and Planner URL)Output/<date>/logs/download.logOutput/<date>/checkpoint.json(while a run is in progress; cleared on success)
Set MISF_DEBUG=1 in .env to enable NDJSON debug traces (debug-fc545e.log).
Tests
python -m pytest
python scripts\validate_config.py
Troubleshooting
| Issue | What to check |
|---|---|
| Login / MFA fails | TOTP secret; system clock (NTP) |
| Facility not found | Facility Name matches Planner search |
| No PDFs downloaded | Attachments exist; tune src/onedrive_planner.py selectors |
| Proxy errors | Proxy IP/Port in input.xlsx; profile %USERPROFILE%\Automation\ChromiumProfile_MISF_Download |
| Browser not found | Install Chromium or set CHROMIUM_PATH in .env (Google Chrome only with MISF_ALLOW_CHROME=1) |
ChromeDriver only supports Chrome version 148 / attach fails |
Install Chromium matching driver version; driver auto-downloads to .drivers/ |
Browser
The automation uses Chromium only (minimized in the background by default). It looks for, in order:
CHROMIUM_PATHorBROWSER_PATHenvironment variablechrome-win/chrome-win/chrome.exe(portable bundle at project root — default for production)- Bundled
chromedriver.exein the same folder (no download needed) - System Chromium under
Program Files\Chromium,%LOCALAPPDATA%\Chromium, Scoop (MISF_USE_INSTALLED_CHROME=1to prefer this overchrome-win) - Google Chrome only when
MISF_ALLOW_CHROME=1
Set MISF_BROWSER_FOREGROUND=1 to show/maximize the browser window. The production .exe runs without a console window; see Output/<date>/logs/download.log.
Chromedriver: webdriver-manager downloads a matching driver. If attach fails, install Chromium that matches your driver or set CHROMIUM_PATH explicitly.
User profile directory: %USERPROFILE%\Automation\ChromiumProfile_MISF_Download
UiPath migration
An equivalent UiPath Studio (Modern) project lives in uipath/MISF_Planner_Download/. Open project.json in UiPath Studio. Pre-flight check:
powershell -File scripts\validate_uipath_pilot.ps1
python -m pytest tests\test_uipath_pilot.py -v
Internal MISF / Access Psychology automation.