ScreenshotNeo

BlogHow-to

How to Fix Selenium Screenshots When Running from Windows Task Scheduler

Fix missing Selenium screenshots in Windows Task Scheduler with headless Chrome, absolute paths, permissions, logging, and reliable cleanup.

By the ScreenshotNeo team1 October 20269 min read

When Selenium saves a screenshot during a manual run but not from Windows Task Scheduler, the browser is usually running in a different execution context. Task Scheduler may use another account, working directory, profile, desktop session, or set of permissions. A reliable fix is to run Chrome headlessly, set a deterministic viewport, save to an absolute path, create the destination with the scheduled account, and log the effective environment.

1. Use this reliable Python pattern first

This example is designed for an unattended scheduled task. It creates the output directory, uses headless Chrome, fixes the viewport, writes to an absolute path, and always closes the browser.

from pathlib import Path
import getpass
import logging
import os
import platform
import sys

from selenium import webdriver
from selenium.webdriver.chrome.options import Options

OUT_DIR = Path(r"C:\Automation\artifacts")
OUT_FILE = OUT_DIR / "example.png"
URL = "https://example.com"

OUT_DIR.mkdir(parents=True, exist_ok=True)

logging.basicConfig(
    filename=str(OUT_DIR / "selenium.log"),
    level=logging.INFO,
    format="%(asctime)s %(levelname)s %(message)s",
)

logging.info("account=%s", getpass.getuser())
logging.info("cwd=%s", os.getcwd())
logging.info("python=%s", sys.version.replace("\\n", " "))
logging.info("platform=%s", platform.platform())
logging.info("url=%s", URL)
logging.info("output=%s", OUT_FILE)

options = Options()
options.add_argument("--headless")
options.add_argument("--window-size=1920,1080")

# Selenium Manager can locate a compatible driver in supported setups.
driver = webdriver.Chrome(options=options)
try:
    driver.get(URL)
    saved = driver.save_screenshot(str(OUT_FILE))
    logging.info("save_screenshot returned=%s exists=%s size=%s", saved, OUT_FILE.exists(), OUT_FILE.stat().st_size if OUT_FILE.exists() else 0)
    if not OUT_FILE.exists():
        raise RuntimeError(f"Screenshot was not created: {OUT_FILE}")
finally:
    driver.quit()

Selenium documents the screenshot filename as the full path to save. The absolute path matters because Task Scheduler does not necessarily start your process in the script directory.

2. Understand what changes under Task Scheduler

Choice Manual run Scheduled run Recommended approach
Desktop session Usually interactive and visible May be non-interactive with no visible desktop Use headless Chrome for unattended tasks
Account Your logged-in account The account configured on the task Grant that account access to the browser profile and output directory
Working directory Often your project directory May be C:\Windows\System32 or another directory Use absolute paths and set Start in
Mapped drives Available in your session Often unavailable Use a local path or a UNC path with credentials
Environment Your PATH, virtual environment, and profile Only the scheduled account’s environment Use the full interpreter path and log versions

Microsoft describes interactive execution as running only when the configured user is logged on. A task configured to run whether the user is logged on or not normally has no visible desktop, and GUI windows are not available for interaction. This is why a headful browser can appear to work manually and fail when scheduled.

3. Configure the task correctly

  1. Program/script: select the full path to Python, for example C:\Users\automation\AppData\Local\Programs\Python\Python312\python.exe.
  2. Add arguments: use the full script path, such as C:\Automation\capture.py.
  3. Start in: set C:\Automation when the Task Scheduler UI provides this field.
  4. Account: choose the account that owns the files and has permission to run Chrome and write the output.
  5. Security option: use “Run only when the user is logged on” only when the automation genuinely requires a visible desktop. Otherwise keep the task non-interactive and use headless Chrome.
  6. History: enable task history and inspect the last run result after each change.

Run the same command in a terminal as the scheduled account if possible. That separates Python, Selenium, browser, and permission errors from Task Scheduler configuration errors.

4. Make Chrome deterministic

Headless mode

Chrome Headless runs without a visible UI and is intended for unattended environments. Add --headless to Selenium’s Chrome options. On installations where a newer headless implementation is required, --headless=new may be appropriate; use the mode supported by the Chrome version deployed on the machine.

Viewport size

Use --window-size=1920,1080 or another explicit size. Without it, screenshots can differ between an interactive desktop and a scheduled session.

Page readiness

driver.get() waits for the page load strategy, but JavaScript applications can continue rendering. Wait for a specific element or a short, justified delay before saving.

from selenium.webdriver.common.by import By
from selenium.webdriver.support.ui import WebDriverWait

# After driver.get(...):
WebDriverWait(driver, 30).until(
    lambda d: d.find_element(By.CSS_SELECTOR, "main")
)
driver.save_screenshot(str(OUT_FILE))

Prefer a meaningful selector over a fixed sleep when the page exposes a stable readiness element. If content is loaded only after scrolling, scroll before capture and wait for the content to appear.

5. Check paths and permissions

Create the directory before the browser starts and test a write using the same account as the task.

from pathlib import Path

path = Path(r"C:\Automation\artifacts\write-test.txt")
path.write_text("scheduler write test\n", encoding="utf-8")
print(path, path.exists())

Do not rely on a mapped drive such as Z:\ in a non-interactive task. Use a local directory or a UNC path such as \\server\share\artifacts\shot.png, and ensure the task account has share and NTFS permissions.

Also check that antivirus or endpoint protection is not quarantining the browser, driver, temporary profile, or output file. Use a dedicated automation profile directory if the machine has profile-locking issues.

6. Log the effective context

A missing file may mean the save failed, or it may mean the file was written somewhere unexpected. Log these values at startup:

  • Account name
  • Current working directory
  • Python and Selenium versions
  • Chrome and driver versions
  • Target URL
  • Exact absolute output path
  • Whether the output exists and its byte size after saving
  • Full exception text and traceback

Redirect standard output and error from a wrapper batch file if needed:

@echo off
C:\Path\To\python.exe C:\Automation\capture.py >> C:\Automation\artifacts\task.log 2>&1

7. Keep Chrome and the driver compatible

Selenium Manager is Selenium’s browser and driver management component and uses Chrome for Testing where applicable. It can simplify deployment when the machine can resolve and obtain compatible binaries. For controlled production environments, pin and deploy known-compatible browser and driver versions, then log both versions so an update can be correlated with a failure.

Do not assume the browser installed for your interactive account is the browser available to the scheduled account. Verify the executable and profile used by the task.

8. Interactive versus non-interactive execution

Choose the execution model deliberately:

  • Unattended capture: run whether the user is logged on or not, use headless Chrome, absolute paths, and file-based logging.
  • Desktop-dependent capture: run only when the user is logged on and use the interactive option. This requires a persistent logged-in session and is less suitable for servers.

Message boxes, prompts, and other UI dependencies can block a non-interactive job indefinitely. Remove them or replace them with logging and explicit exit codes.

9. Troubleshooting common failures

Symptom Likely cause Fix
No PNG and no visible error Output path is relative or points to another working directory Use an absolute path, log os.getcwd(), and check the directory used by the task
PermissionError Scheduled account cannot write the destination Create the directory and grant write permission to the task account
Chrome does not start Driver/browser mismatch, profile lock, or unavailable executable Align versions, use Selenium Manager or pinned binaries, and use a dedicated profile
Task says it ran successfully but file is absent Script swallowed an exception or saved elsewhere Log the traceback, return a nonzero exit code, and verify the exact path after saving
DevToolsActivePort or startup crash Headful browser in a non-interactive session or restricted temporary directory Use headless mode, a writable temporary/profile directory, and confirm account permissions
Screenshot is blank Page failed, was blocked, or capture happened before rendering Log the URL and page title, wait for a readiness selector, and inspect page source or browser logs
Dynamic content is missing Capture occurs before asynchronous rendering finishes Wait for a stable element or network-dependent condition instead of guessing with a long sleep
Different dimensions than manual run Viewport depends on the desktop session Add an explicit --window-size argument and keep device settings fixed
Mapped-drive file is missing Drive mapping exists only in the interactive session Use a local path or UNC path and grant both share and NTFS permissions
Task hangs Dialog, prompt, browser process, or page load never finishes Remove UI prompts, set page and script timeouts, and always call driver.quit() in finally

10. Add timeouts and clean shutdown

from selenium.common.exceptions import TimeoutException

options = Options()
options.add_argument("--headless")
options.add_argument("--window-size=1920,1080")
driver = webdriver.Chrome(options=options)
try:
    driver.set_page_load_timeout(60)
    driver.set_script_timeout(30)
    driver.get(URL)
    driver.save_screenshot(str(OUT_FILE))
except TimeoutException:
    logging.exception("Timed out while loading or rendering %s", URL)
    raise
finally:
    driver.quit()

Use a finally block even when the screenshot fails. It prevents orphaned Chrome processes from accumulating across scheduled runs.

11. Performance, reliability, and cost considerations

  • Startup cost: launching Chrome for every URL is slower than reusing one driver, but a fresh process isolates failures. Reuse a driver only when you can reset cookies, local storage, and page state safely.
  • Wait strategy: a selector-based wait usually finishes sooner and is more reliable than a large fixed sleep.
  • Resource use: limit concurrent browsers on small machines; each instance consumes memory and temporary disk space.
  • Retries: retry transient navigation failures with a bounded count and delay. Do not retry indefinitely or create duplicate artifacts without unique names.
  • Artifact naming: include a job ID or timestamp when several runs can overlap. Write to a temporary filename and rename after a successful capture if consumers must never see partial files.
  • Cost: Selenium itself has no per-screenshot API charge, but scheduled infrastructure consumes CPU, memory, storage, and maintenance time. Browser and driver updates are part of the operational cost.

12. Or skip the browser setup

ScreenshotNeo provides a website screenshot API and MCP server when you do not want to maintain Chrome, ChromeDriver, profiles, and Task Scheduler desktop behavior. It accepts a URL and returns PNG, JPEG, WebP, or PDF. Before capture, it accepts cookie or consent banners and removes more than 60 known consent platforms, newsletter popups, and chat widgets; each step can be turned off.

Only clean shots are billed. Bot checks or CAPTCHAs, blank pages, timeouts, failed loads, and cache hits cost nothing, and the response identifies the result with X-Page-Verdict and X-Billed headers.

cURL

curl -G "https://api.screenshotneo.com/v1/shot" -d access_key=YOUR_API_KEY --data-urlencode url=https://example.com -o shot.webp

Python

import requests

r = requests.get(
    "https://api.screenshotneo.com/v1/shot",
    params={"access_key": "YOUR_API_KEY", "url": "https://example.com"},
    timeout=90,
)
r.raise_for_status()
open("shot.webp", "wb").write(r.content)

Node.js

const q = new URLSearchParams({ access_key: 'YOUR_API_KEY', url: 'https://example.com' });
const res = await fetch(`https://api.screenshotneo.com/v1/shot?${q}`);
if (!res.ok) throw new Error(`Screenshot failed: ${res.status}`);
const fs = await import('node:fs/promises');
await fs.writeFile('shot.webp', Buffer.from(await res.arrayBuffer()));

See the ScreenshotNeo API documentation for the request options. It supports full-page capture with lazy images loaded, CSS element capture, dark mode, device presets and custom viewports, retina scale, custom CSS and JavaScript, click and wait actions, blocked requests, headers, cookies, user agents, authorization, timezone, geolocation, transparent backgrounds, resizing, cache TTLs, signed links, asynchronous jobs with signed webhooks, bulk capture, usage data, and PDFs.

An MCP server provides take_screenshot, get_page_info, and capture_pdf tools for Claude, Cursor, and other MCP clients. There are 1,000 free screenshots each month with no card; paid plans start at $5 for 3,000 shots. Create a free ScreenshotNeo account.

13. Verification checklist

  • Run the exact scheduled command manually as the scheduled account.
  • Use headless Chrome for non-interactive execution.
  • Set a fixed window size.
  • Use an absolute local or UNC output path.
  • Create the destination directory before capture.
  • Grant the task account write permission.
  • Set the task’s interpreter, script path, and Start in directory explicitly.
  • Log account, working directory, versions, URL, output path, and exceptions.
  • Wait for a real page-ready condition.
  • Call driver.quit() in cleanup.
  • Inspect Task Scheduler history and the generated log.

FAQ

Does Selenium require an interactive Windows session to take screenshots?

No. Headless Chrome is designed for unattended execution. An interactive session is needed only when the automation depends on a visible desktop or other GUI interaction.

Why does changing the output filename not fix the problem?

The filename may still be relative. Task Scheduler can start in a different directory, so use the full path and log the resolved location.

Should I use a mapped drive for screenshot storage?

A mapped drive may not exist in a non-interactive session. Prefer a local directory or a properly authorized UNC path.

Is a fixed sleep enough for dynamic pages?

It can work for a known page, but waiting for a stable selector is usually faster and less sensitive to machine load.

When is an API preferable to Selenium?

An API is useful when you need repeatable captures without managing a browser, driver, profile, desktop session, or scheduled machine. ScreenshotNeo is one option with a free monthly tier and per-response billing status headers.