From e6c34ef76004d4924b9e25d3aa0203e6031d940c Mon Sep 17 00:00:00 2001 From: "Hoid (OpenClaw)" Date: Wed, 4 Mar 2026 18:04:18 +0100 Subject: [PATCH] Add comprehensive tests and docs for darkMode & hideSelectors - Add 5 new Python tests for darkMode and hideSelectors parameters - Update Node.js SDK README with darkMode/hideSelectors examples - Update Python SDK README with darkMode/hideSelectors examples - Add API reference entries for new parameters - All tests passing: Node.js (19 tests), Python (22 tests) Features already implemented in v0.7.0 but needed better test coverage and documentation. --- sdk/node/README.md | 47 ++++++++++++ sdk/python/README.md | 48 +++++++++++++ sdk/python/tests/test_snapapi.py | 119 +++++++++++++++++++++++++++++++ 3 files changed, 214 insertions(+) diff --git a/sdk/node/README.md b/sdk/node/README.md index eb7ee63..140c030 100644 --- a/sdk/node/README.md +++ b/sdk/node/README.md @@ -72,6 +72,51 @@ const screenshot = await snap.capture({ }); ``` +### Dark Mode Capture + +```typescript +// Capture in dark mode (prefers-color-scheme: dark) +const darkScreenshot = await snap.capture({ + url: 'https://example.com', + darkMode: true, + format: 'png', +}); +``` + +### Hide Elements Before Capture + +```typescript +// Hide cookie banners, popups, ads +const cleanScreenshot = await snap.capture({ + url: 'https://example.com', + hideSelectors: [ + '.cookie-banner', + '.popup-overlay', + '#advertisement', + '.tracking-notice' + ], +}); + +// Hide single element +const singleHide = await snap.capture('https://example.com', { + hideSelectors: '.newsletter-popup', +}); +``` + +### Combined Dark Mode + Element Hiding + +```typescript +// Perfect for clean marketing screenshots +const marketingShot = await snap.capture({ + url: 'https://your-saas-app.com', + darkMode: true, + hideSelectors: ['.dev-banner', '.beta-notice'], + width: 1920, + height: 1080, + deviceScale: 2, +}); +``` + ## API Reference ### `new SnapAPI(apiKey, config?)` @@ -98,6 +143,8 @@ Returns a `Promise` containing the screenshot image. | `deviceScale` | `number` | `1` | Device pixel ratio (1–3) | | `delay` | `number` | `0` | Extra delay in ms (0–5000) | | `waitUntil` | `string` | `'domcontentloaded'` | Load event to wait for | +| `darkMode` | `boolean` | `false` | Emulate prefers-color-scheme: dark | +| `hideSelectors` | `string \| string[]` | — | CSS selectors to hide before capture | ### `snap.health()` diff --git a/sdk/python/README.md b/sdk/python/README.md index c7bfed8..ef0dd82 100644 --- a/sdk/python/README.md +++ b/sdk/python/README.md @@ -75,6 +75,52 @@ screenshot = snap.capture( ) ``` +### Dark Mode Capture + +```python +# Capture in dark mode (prefers-color-scheme: dark) +dark_screenshot = snap.capture( + "https://example.com", + dark_mode=True, + format="png", +) +``` + +### Hide Elements Before Capture + +```python +# Hide cookie banners, popups, ads +clean_screenshot = snap.capture( + "https://example.com", + hide_selectors=[ + ".cookie-banner", + ".popup-overlay", + "#advertisement", + ".tracking-notice" + ], +) + +# Hide single element +single_hide = snap.capture( + "https://example.com", + hide_selectors=".newsletter-popup", +) +``` + +### Combined Dark Mode + Element Hiding + +```python +# Perfect for clean marketing screenshots +marketing_shot = snap.capture( + "https://your-saas-app.com", + dark_mode=True, + hide_selectors=[".dev-banner", ".beta-notice"], + width=1920, + height=1080, + device_scale=2, +) +``` + ### Error Handling ```python @@ -106,6 +152,8 @@ except SnapAPIError as e: | `device_scale` | `float` | `1` | Device pixel ratio (1–3) | | `delay` | `int` | `0` | Extra delay in ms (0–5000) | | `wait_until` | `str` | `"domcontentloaded"` | Load event | +| `dark_mode` | `bool` | `False` | Emulate prefers-color-scheme: dark | +| `hide_selectors` | `list` | — | CSS selectors to hide before capture | ### `snap.health() -> dict` diff --git a/sdk/python/tests/test_snapapi.py b/sdk/python/tests/test_snapapi.py index 8ca56ab..984c537 100644 --- a/sdk/python/tests/test_snapapi.py +++ b/sdk/python/tests/test_snapapi.py @@ -155,6 +155,91 @@ class TestSnapAPI(unittest.TestCase): self.assertEqual(request_body, expected_body) self.assertEqual(result, b'fake-image-data') + @patch('snapapi.client.urllib.request.urlopen') + def test_capture_with_dark_mode_parameter(self, mock_urlopen): + """capture with dark_mode parameter should work correctly.""" + # Mock response + mock_response = Mock() + mock_response.read.return_value = b'fake-dark-image-data' + mock_response.__enter__ = Mock(return_value=mock_response) + mock_response.__exit__ = Mock(return_value=None) + mock_urlopen.return_value = mock_response + + result = self.snap.capture( + url="https://example.com", + dark_mode=True + ) + + # Verify request body contains darkMode + request_arg = mock_urlopen.call_args[0][0] + request_body = json.loads(request_arg.data.decode()) + + expected_body = { + "url": "https://example.com", + "darkMode": True + } + + self.assertEqual(request_body, expected_body) + self.assertEqual(result, b'fake-dark-image-data') + + @patch('snapapi.client.urllib.request.urlopen') + def test_capture_with_hide_selectors_list_parameter(self, mock_urlopen): + """capture with hide_selectors as list should work correctly.""" + # Mock response + mock_response = Mock() + mock_response.read.return_value = b'fake-clean-image-data' + mock_response.__enter__ = Mock(return_value=mock_response) + mock_response.__exit__ = Mock(return_value=None) + mock_urlopen.return_value = mock_response + + result = self.snap.capture( + url="https://example.com", + hide_selectors=['.ads', '.popup', '#cookie-banner'] + ) + + # Verify request body contains hideSelectors + request_arg = mock_urlopen.call_args[0][0] + request_body = json.loads(request_arg.data.decode()) + + expected_body = { + "url": "https://example.com", + "hideSelectors": ['.ads', '.popup', '#cookie-banner'] + } + + self.assertEqual(request_body, expected_body) + self.assertEqual(result, b'fake-clean-image-data') + + @patch('snapapi.client.urllib.request.urlopen') + def test_capture_with_both_dark_mode_and_hide_selectors(self, mock_urlopen): + """capture with both dark_mode and hide_selectors should work correctly.""" + # Mock response + mock_response = Mock() + mock_response.read.return_value = b'fake-dark-clean-image-data' + mock_response.__enter__ = Mock(return_value=mock_response) + mock_response.__exit__ = Mock(return_value=None) + mock_urlopen.return_value = mock_response + + result = self.snap.capture( + url="https://example.com", + dark_mode=True, + hide_selectors=['.tracking', '.ads'], + format="png" + ) + + # Verify request body contains both parameters + request_arg = mock_urlopen.call_args[0][0] + request_body = json.loads(request_arg.data.decode()) + + expected_body = { + "url": "https://example.com", + "darkMode": True, + "hideSelectors": ['.tracking', '.ads'], + "format": "png" + } + + self.assertEqual(request_body, expected_body) + self.assertEqual(result, b'fake-dark-clean-image-data') + def test_capture_raises_value_error_if_no_url(self): """capture() should raise ValueError if no url provided.""" with self.assertRaises(ValueError) as cm: @@ -364,6 +449,40 @@ class TestScreenshotOptions(unittest.TestCase): expected = {"url": "https://example.com"} self.assertEqual(result, expected) + def test_to_dict_with_dark_mode_and_hide_selectors(self): + """to_dict() should correctly handle darkMode and hideSelectors.""" + options = ScreenshotOptions( + url="https://example.com", + dark_mode=True, + hide_selectors=['.ads', '.popup', '#banner'] + ) + + result = options.to_dict() + + expected = { + "url": "https://example.com", + "darkMode": True, # snake_case -> camelCase + "hideSelectors": ['.ads', '.popup', '#banner'] # snake_case -> camelCase + } + + self.assertEqual(result, expected) + + def test_to_dict_with_dark_mode_false(self): + """to_dict() should include darkMode when explicitly set to False.""" + options = ScreenshotOptions( + url="https://example.com", + dark_mode=False + ) + + result = options.to_dict() + + expected = { + "url": "https://example.com", + "darkMode": False + } + + self.assertEqual(result, expected) + class TestSnapAPIError(unittest.TestCase): """Test cases for SnapAPIError exception."""