Use a per-user launchd LaunchAgent to run a browser automation script on a calendar schedule, then save the browser-rendered page image to a stable path. A website screenshot and a screenshot of the visible Mac desktop are different tasks: Playwright is suited to capturing a web page, while Apple’s ScreenCaptureKit captures screen content and may require Screen Recording permission.
Choose what you want to capture
For a website screenshot, automate a browser to navigate to the page and save an image. Browser APIs can capture the viewport or the full scrollable page. This is generally the right approach for a clean page image that does not include the Mac desktop, menu bar, or other windows.
As an Amazon Associate I earn from qualifying purchases.
For an image of what is displayed on the Mac, including an app window or display, use a screen-content capture approach such as Apple’s ScreenCaptureKit. Apple’s sample documents a first-use Screen Recording prompt. This is separate from the browser page screenshot workflow.
Do these 3 things before closing this tab:
1Clear out junk files and repair common Windows errors2Scan for outdated or missing drivers - takes under a minute3Repair Windows errors before they cause bigger problems| Approach | What it captures | Automation and output | Permission context |
|---|---|---|---|
| Browser page screenshot | A rendered website page | Browser automation navigates to the URL; can save the viewport or full page. | Browser automation setup; macOS Screen Recording permission is not the defining requirement. |
| ScreenCaptureKit | Screen content such as displays, apps, and windows | Captures what is displayed rather than asking a browser page API for a full-page image. | Apple’s sample documents a first-use Screen Recording prompt. |
How a scheduled LaunchAgent works
Apple’s archived Daemons and Services Programming Guide recommends launchd for timed jobs and describes a separate property list (plist) for each job. A LaunchAgent runs on behalf of the logged-in user; a LaunchDaemon runs in a system context and may run before a user logs in. For website captures, a user agent is often the practical choice when the script depends on that user’s browser state, credentials, or writable output directory. Do not assume a browser session or credentials will be available to a background job just because they work in an interactive session.
#1 Best Overall
The plist needs a unique Label, a ProgramArguments array specifying the command and its arguments, and a StartCalendarInterval schedule. For calendar intervals, omitted time fields act as wildcards. Apple’s detailed scheduling examples are in archived documentation; treat them as an explanation of the documented model, not as a guarantee that every detail is unchanged on every current macOS release. Apple says that apps managing their own helpers can use SMAppService to register and control LaunchAgents and LaunchDaemons beginning with macOS 13; that is distinct from manually creating a user plist.
Build a browser capture script
Playwright’s page screenshot API writes an image to a chosen path. Set fullPage: true to capture the full scrollable page instead of only the current viewport. The following example is a Node.js script for a viewport capture; replace the URL and output directory with values appropriate for the Mac account that will run the agent.
Rank #2
- Intuitive interface of a conventional FTP client
- Easy and Reliable FTP Site Maintenance.
- FTP Automation and Synchronization
- Install Node.js, then create a project directory and install Playwright:
npm init -yfollowed bynpm install playwright. - Install the browser Playwright uses:
npx playwright install chromium. - Save this as
capture.mjsin that project directory:import { chromium } from 'playwright'; const url = process.env.SCREENSHOT_URL ?? 'https://example.com'; const output = process.env.SCREENSHOT_PATH ?? `${process.env.HOME}/Screenshots/site.png`; const browser = await chromium.launch({ headless: true }); try { const page = await browser.newPage({ viewport: { width: 1440, height: 1000 } }); await page.goto(url, { waitUntil: 'networkidle', timeout: 60000 }); await page.screenshot({ path: output, fullPage: false }); } finally { await browser.close(); } - Create the output directory before scheduling the job, for example with
mkdir -p "$HOME/Screenshots". Confirm that the logged-in user can write to it.
To capture the entire scrollable page, change the screenshot call to await page.screenshot({ path: output, fullPage: true });. Long or dynamically loaded pages may need an explicit wait for a selector or a short delay before capture; network idle is not a universal signal that every site’s late-loading content is finished.
Create the recurring LaunchAgent plist
For a daily capture at 9:30 a.m. local time, create ~/Library/LaunchAgents/com.example.website-screenshot.plist. Replace /Users/yourname with the actual home-directory path. The example invokes Node and the script by absolute path so the job does not depend on an interactive shell’s working directory or PATH.
Rank #3
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<dict>
<key>Label</key>
<string>com.example.website-screenshot</string>
<key>ProgramArguments</key>
<array>
<string>/opt/homebrew/bin/node</string>
<string>/Users/yourname/website-capture/capture.mjs</string>
</array>
<key>StartCalendarInterval</key>
<dict>
<key>Hour</key>
<integer>9</integer>
<key>Minute</key>
<integer>30</integer>
</dict>
<key>StandardOutPath</key>
<string>/Users/yourname/Library/Logs/website-screenshot.log</string>
<key>StandardErrorPath</key>
<string>/Users/yourname/Library/Logs/website-screenshot-error.log</string>
</dict>
</plist>
The Node executable path varies by installation. Find the correct path in the environment where Node is installed, then put that absolute path in the first ProgramArguments item. The script’s output path is separately set by SCREENSHOT_PATH in the example, or defaults to ~/Screenshots/site.png. The plist above does not set environment variables; use a fixed output path in the script, or add a EnvironmentVariables dictionary with SCREENSHOT_PATH and SCREENSHOT_URL if you want to configure them in the job.
To run on a different calendar schedule, change the fields in StartCalendarInterval. For example, setting Weekday as well as Hour and Minute selects a weekday schedule; omitted fields are wildcards in Apple’s documented model. Avoid reusing a Label already assigned to another job.
Rank #4
Load, test, and inspect the job
- Check that the plist is valid XML:
plutil -lint ~/Library/LaunchAgents/com.example.website-screenshot.plist. Correct any reported syntax error before loading it. - Load the agent for the current user:
launchctl bootstrap gui/$(id -u) ~/Library/LaunchAgents/com.example.website-screenshot.plist. - Request an immediate run to test it:
launchctl kickstart -k gui/$(id -u)/com.example.website-screenshot. - Check that the expected image exists and opens, then inspect the log files named in the plist if it does not.
- When changing the plist, boot it out and bootstrap it again:
launchctl bootout gui/$(id -u) ~/Library/LaunchAgents/com.example.website-screenshot.plist, then repeat the bootstrap command.
The test run checks whether the executable, script, browser, URL, and output location work under the job’s context. It does not establish that a future run will occur at an exact wall-clock time if the Mac sleeps or is shut down.
Account for sleep, shutdown, and login state
Apple’s archived guide states: “If you schedule a launchd job by setting the StartCalendarInterval key and the computer is asleep when the job should have run, your job will run when the computer wakes up.” A powered-off Mac is different: a calendar occurrence missed while it is off waits until the next scheduled time. Do not promise an exact-time capture when the Mac may sleep or be shut down.
Best Value
A LaunchAgent runs for a logged-in user. If the workflow depends on interactive browser state, logged-in website credentials, or a user’s home-directory files, verify that these are accessible in the job context. A LaunchDaemon may run before login, but that does not automatically give it access to a user’s browser session.
Independent reader supportYour contribution helps us test, update, and keep practical guides available for everyone.Use Safari remote automation when Safari is required
If the capture must use Safari rather than a browser controlled directly through another automation library, Apple’s WebDriver instructions provide a Safari-specific route. In Safari, turn on Allow remote automation in Developer Settings, or enable it from Terminal with safaridriver --enable. This is an option for Safari automation, not a prerequisite for every browser screenshot workflow.
Keep recurring captures comparable
If the purpose is to compare screenshots over time, hold the environment steady where practical: use the same browser, viewport, device scale, and capture mode. Playwright warns that OS version, settings, hardware, power source, and headless mode can affect rendered screenshots. Changes to the site itself can also change the result; a difference image alone does not explain its cause.
Windows Errors? Fix Them Before They Spread
Repair common Windows errors and clear accumulated junk for a smoother, more stable PC - no reinstall needed.Free scan · no reinstallCrashes, No Sound, or Screen Glitches?
Random freezes, missing sound and display glitches usually trace back to one bad driver. Find and replace yours safely.Free scan · under a minuteOr skip the browser setup
ScreenshotNeo provides a website screenshot API and MCP server. One GET request can return an image or PDF; the example below saves a WebP response. See the ScreenshotNeo API documentation for request options.
curl -G "https://api.screenshotneo.com/v1/shot" -d access_key=YOUR_API_KEY --data-urlencode url=https://example.com -o shot.webp
ScreenshotNeo accepts cookie and consent banners before capture and removes more than 60 known consent platforms, newsletter popups, and chat widgets; each of those steps can be turned off. Bot checks or CAPTCHAs, blank pages, timeouts, failed loads, and cache hits are not billed, with response headers indicating the page verdict and billing status. Its MCP server offers take_screenshot, get_page_info, and capture_pdf for AI agents. The Free plan includes 1,000 screenshots a month with no card; paid plans start at $5 for 3,000 screenshots.
Sign up free for 1,000 screenshots a month—no card required.
Quick Recap
Troubleshooting
- The plist fails validation: Run
plutil -lintand correct malformed XML, mismatched tags, or invalid value types. - The job loads but produces no screenshot: Check the standard error log, verify the absolute Node and script paths, ensure Playwright’s browser is installed for the relevant account, and confirm the output directory is writable.
- The script works in Terminal but not as a job: A LaunchAgent has a different environment from an interactive shell. Use absolute executable and script paths; do not rely on shell startup files or a relative working directory.
- The image is only the visible portion of the page: Set Playwright’s
fullPageoption totrue; the default example captures only the viewport. - The capture misses delayed content: Wait for a meaningful page selector or content state before taking the screenshot. A generic network-idle condition may not cover every site’s deferred rendering.
- The capture did not happen at the scheduled minute: If the Mac was asleep, Apple documents that the missed calendar job runs on wake. If it was powered off, that occurrence is skipped until the next scheduled time.
- Safari automation cannot connect: Enable Allow remote automation in Safari Developer Settings or run
safaridriver --enable.
Product prices and availability are accurate as of the date/time indicated and are subject to change. Any price and availability information displayed on Amazon at the time of purchase will apply.
Free tools Windows power users keep installed
One-click scans. No signup required.




