diff --git a/docs/javascript.md b/docs/javascript.md index aab8a2f..a3b022a 100644 --- a/docs/javascript.md +++ b/docs/javascript.md @@ -238,6 +238,8 @@ Options: --browser-arg TEXT Additional arguments to pass to the browser --user-agent TEXT User-Agent header to use --reduced-motion Emulate 'prefers-reduced-motion' media feature + --color-scheme [light|dark|no-preference] + Emulate 'prefers-color-scheme' media feature --log-console Write console.log() to stderr --fail Fail with an error code if a page returns an HTTP error diff --git a/docs/multi.md b/docs/multi.md index 60ffe6b..ad36382 100644 --- a/docs/multi.md +++ b/docs/multi.md @@ -242,6 +242,8 @@ Options: --browser-arg TEXT Additional arguments to pass to the browser --user-agent TEXT User-Agent header to use --reduced-motion Emulate 'prefers-reduced-motion' media feature + --color-scheme [light|dark|no-preference] + Emulate 'prefers-color-scheme' media feature --log-console Write console.log() to stderr --fail Fail with an error code if a page returns an HTTP error diff --git a/docs/screenshots.md b/docs/screenshots.md index 0c1cb56..7e9d156 100644 --- a/docs/screenshots.md +++ b/docs/screenshots.md @@ -159,6 +159,16 @@ The `--omit-background` option instructs the browser to ignore the default backg shot-scraper https://simonwillison.net/ -o simon.png \ --width 400 --height 600 --omit-background ``` +## Emulating color scheme preference + +Use `--color-scheme` to emulate the `prefers-color-scheme` media feature. This is useful for capturing screenshots of pages that support dark mode: + +```bash +shot-scraper https://example.com/ -o dark.png --color-scheme dark +``` + +The available values are `light`, `dark`, and `no-preference`. + ## Interacting with the page Sometimes it's useful to be able to manually interact with a page before the screenshot is captured. @@ -366,6 +376,8 @@ Options: --browser-arg TEXT Additional arguments to pass to the browser --user-agent TEXT User-Agent header to use --reduced-motion Emulate 'prefers-reduced-motion' media feature + --color-scheme [light|dark|no-preference] + Emulate 'prefers-color-scheme' media feature --fail Fail with an error code if a page returns an HTTP error --skip Skip pages that return HTTP errors diff --git a/shot_scraper/cli.py b/shot_scraper/cli.py index d12e601..eccce77 100644 --- a/shot_scraper/cli.py +++ b/shot_scraper/cli.py @@ -23,6 +23,7 @@ ) BROWSERS = ("chromium", "firefox", "webkit", "chrome", "chrome-beta") +COLOR_SCHEMES = ("light", "dark", "no-preference") def console_log(msg): @@ -140,6 +141,16 @@ def reduced_motion_option(fn): return fn +def color_scheme_option(fn): + click.option( + "--color-scheme", + type=click.Choice(COLOR_SCHEMES, case_sensitive=False), + default=None, + help="Emulate 'prefers-color-scheme' media feature", + )(fn) + return fn + + @click.group( cls=DefaultGroup, default="shot", @@ -248,6 +259,7 @@ def cli(): @browser_args_option @user_agent_option @reduced_motion_option +@color_scheme_option @skip_fail_options @bypass_csp_option @silent_option @@ -279,6 +291,7 @@ def shot( browser_args, user_agent, reduced_motion, + color_scheme, skip, fail, bypass_csp, @@ -349,6 +362,7 @@ def shot( user_agent=user_agent, timeout=timeout, reduced_motion=reduced_motion, + color_scheme=color_scheme, bypass_csp=bypass_csp, auth_username=auth_username, auth_password=auth_password, @@ -404,6 +418,7 @@ def _browser_context( user_agent=None, timeout=None, reduced_motion=False, + color_scheme=None, bypass_csp=False, auth_username=None, auth_password=None, @@ -433,6 +448,8 @@ def _browser_context( context_args["device_scale_factor"] = scale_factor if reduced_motion: context_args["reduced_motion"] = "reduce" + if color_scheme: + context_args["color_scheme"] = color_scheme if user_agent is not None: context_args["user_agent"] = user_agent if bypass_csp: @@ -487,6 +504,7 @@ def _browser_context( @browser_args_option @user_agent_option @reduced_motion_option +@color_scheme_option @log_console_option @skip_fail_options @silent_option @@ -525,6 +543,7 @@ def multi( browser_args, user_agent, reduced_motion, + color_scheme, log_console, skip, fail, @@ -582,6 +601,7 @@ def multi( user_agent=user_agent, timeout=timeout, reduced_motion=reduced_motion, + color_scheme=color_scheme, auth_username=auth_username, auth_password=auth_password, record_har_path=har_file or None, @@ -964,6 +984,7 @@ def _extract_har_entry(entry, extract_dir, existing_files, file_exists_fn, zip_f @browser_args_option @user_agent_option @reduced_motion_option +@color_scheme_option @log_console_option @skip_fail_options @bypass_csp_option @@ -979,6 +1000,7 @@ def javascript( browser_args, user_agent, reduced_motion, + color_scheme, log_console, skip, fail, @@ -1035,6 +1057,7 @@ def javascript( browser_args=browser_args, user_agent=user_agent, reduced_motion=reduced_motion, + color_scheme=color_scheme, bypass_csp=bypass_csp, auth_username=auth_username, auth_password=auth_password, diff --git a/tests/test_shot_scraper.py b/tests/test_shot_scraper.py index 9e7f2ba..84e2125 100644 --- a/tests/test_shot_scraper.py +++ b/tests/test_shot_scraper.py @@ -208,6 +208,36 @@ def test_html(args, expected): assert result.output.replace("\n", "") == expected.replace("\n", "") +@pytest.mark.parametrize( + "command", + ("shot", "multi", "javascript"), +) +def test_color_scheme_invalid(command): + runner = CliRunner() + result = runner.invoke(cli, [command, "-", "--color-scheme", "invalid"]) + assert result.exit_code != 0 + assert "Invalid value" in result.output + + +@pytest.mark.parametrize("color_scheme", ("light", "dark", "no-preference")) +def test_javascript_color_scheme(color_scheme): + runner = CliRunner() + with runner.isolated_filesystem(): + open("index.html", "w").write(TEST_HTML) + result = runner.invoke( + cli, + [ + "javascript", + "index.html", + "document.title", + "--color-scheme", + color_scheme, + ], + ) + assert result.exit_code == 0, str(result.exception) + assert result.output == '"Test title"\n' + + @pytest.mark.parametrize( "command,args,expected", [