Shipped a py.typed marker (PEP 561) and the Typing :: Typed classifier, so type checkers (mypy, pyright) recognize Hermex as a typed package — consumers now get full type information for the public API instead of Any
Complete type annotations across the public API and internal helpers, covering both parameters and return types
Parameters that accept None (e.g. attachments, typing_delay, timeout) are now annotated as explicit X | None instead of implicit-Optional, so strict type checkers no longer flag them
clear_data() resolves its default data directory the same way as __init__() and setup() (via a None sentinel) rather than binding the default at import time; behavior is unchanged
ChatGPT._upload_files() and Gemini._upload_files() now restore the file input's original display style after upload instead of leaving the temporary display: block override on the DOM — the override was permanent on ChatGPT's persistent #upload-photos input. The restore runs in a finally block so it happens even if the upload fails
ChatGPT.get_last_response() no longer crashes when the response includes web-search result images — the previous img selector grabbed the first <img> in the turn, so a web-search thumbnail was clicked instead of the generated image, the "Save" button never appeared, and wait.until(...) raised an uncaught TimeoutException. The selector is now scoped to [class*="imagegen-image"] img, which matches only the generated-image wrapper and skips the group/search-image thumbnails
Gemini.get_state() rewritten for the latest UI: the send/stop button moved out of [data-node-type="input-area"] and the mic-button container is no longer the source of truth for whether the input has content. State is now derived from the [data-test-id="send-button-container"] element — stop class → GENERATING, missing has-input class → IDLE, aria-disabled="true" → UPLOADING, otherwise TYPING. Works on both desktop and mobile layouts
Gemini.get_last_response() now waits ~1.5 s before clicking the image download button, giving the button time to become responsive after the response finishes rendering
send_message() argument order changed — submit moved to the end of the parameter list. Calls passing submit positionally must be updated; keyword calls are unaffected
query() and simple_query() argument order changed — timeout moved to the end of the parameter list (the two now share an identical parameter order). Calls passing timeout positionally must be updated; keyword calls are unaffected
Docstrings for shared arguments aligned across send_message(), query(), and simple_query() for consistency
ChatGPT.send_message() and Gemini.send_message() signatures aligned with the base Scraper.send_message() (added type hints)
Fluent Scraper methods (those returning self, e.g. open_url(), send_message(), short_wait()) are now annotated with typing.Self, so type checkers preserve the concrete subclass type through chained calls
Gemini file upload updated for the latest UI: the upload-menu button's aria-label changed from Open upload file menu to Upload & tools, and the "Upload files" trigger is now located via the shared images-files-uploader[data-test-id="uploader-images-files-button-advanced"] wrapper so it resolves correctly in both the maximized and narrow/mobile menu layouts
Gemini file upload now restores the patched HTMLInputElement.prototype.click even when an intermediate step fails (best-effort restore in try/finally), preventing the override from leaking into the page without masking the original error
Corrected documentation for ChatGPT login requirements — file upload works without login; only image generation requires a logged-in session
ChatGPT.get_state() now correctly returns GENERATING during image generation — after a recent ChatGPT UI update, the stop button disappears while the image loading skeleton is still visible, causing the state to appear idle prematurely; fixed by additionally checking for [data-testid="image-gen-loading-state"]
ChatGPT.get_last_response() no longer raises a spurious "neither text nor image" error on image-only responses — the <img> tag is now awaited for up to 5 seconds to account for the brief DOM delay after the loading skeleton clears
ChatGPT._detect_login() now uses WebDriverWait with TimeoutException instead of a bare find_element with except Exception, consistent with Gemini's approach
_detect_chrome_version() now raises a clear RuntimeError when Chrome is not found, the subprocess fails, or the version string cannot be parsed — previously crashed with a cryptic TypeError or AttributeError
close() now calls self._temp_dir.cleanup() to release the temporary download directory
setup() browser-close detection now catches WebDriverException instead of bare Exception, and wraps the loop in try/finally to guarantee close() is always called
_get_downloaded_file() now filters out .crdownload partial files to avoid returning incomplete downloads
simple_query() now wraps the query in try/finally so the browser is always closed even if an exception is raised
File existence is now checked before upload in both scrapers — raises FileNotFoundError with a clear message instead of a cryptic driver error
TemporaryDirectory creation moved from __init__ to _initialize_driver() so its lifecycle matches the driver — reusing an instance after close() no longer points Chrome at a deleted download directory, and calling close() twice is now a safe no-op