├── .github ├── FUNDING.yml ├── ISSUE_TEMPLATE │ ├── bug_report.md │ └── feature_request.md └── workflows │ ├── codeql-analysis.yml │ └── pythonpackage.yml ├── LICENSE ├── README.md ├── Screenshot ├── Screenshot.py ├── __init__.py └── __pycache__ │ ├── Screenshot_Clipping.cpython-36.pyc │ └── __init__.cpython-36.pyc ├── examples ├── capture_element.py └── capture_full_page.py ├── requirements.txt ├── setup.py └── test ├── __init__.py ├── __pycache__ ├── __init__.cpython-36.pyc ├── test_IEdriver.cpython-36-PYTEST.pyc └── test_chrome_driver.cpython-36-PYTEST.pyc └── test_screenshot.py /.github/FUNDING.yml: -------------------------------------------------------------------------------- 1 | # These are supported funding model platforms 2 | 3 | 4 | github: sam4u3 5 | 6 | custom: https://www.paypal.me/sam4u3 # Replace with up to 4 custom sponsorship URLs e.g., ['link1', 'link2'] 7 | -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/bug_report.md: -------------------------------------------------------------------------------- 1 | --- 2 | name: Bug report 3 | about: Create a report to help us improve 4 | title: '' 5 | labels: '' 6 | assignees: '' 7 | 8 | --- 9 | 10 | **Describe the bug** 11 | A clear and concise description of what the bug is. 12 | 13 | **To Reproduce** 14 | Steps to reproduce the behavior: 15 | 1. Go to '...' 16 | 2. Click on '....' 17 | 3. Scroll down to '....' 18 | 4. See error 19 | 20 | **Expected behavior** 21 | A clear and concise description of what you expected to happen. 22 | 23 | **Screenshots** 24 | If applicable, add screenshots to help explain your problem. 25 | 26 | **Desktop (please complete the following information):** 27 | - OS: [e.g. iOS] 28 | - Browser [e.g. chrome, safari] 29 | - Version [e.g. 22] 30 | 31 | **Smartphone (please complete the following information):** 32 | - Device: [e.g. iPhone6] 33 | - OS: [e.g. iOS8.1] 34 | - Browser [e.g. stock browser, safari] 35 | - Version [e.g. 22] 36 | 37 | **Additional context** 38 | Add any other context about the problem here. 39 | -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/feature_request.md: -------------------------------------------------------------------------------- 1 | --- 2 | name: Feature request 3 | about: Suggest an idea for this project 4 | title: '' 5 | labels: '' 6 | assignees: '' 7 | 8 | --- 9 | 10 | **Is your feature request related to a problem? Please describe.** 11 | A clear and concise description of what the problem is. Ex. I'm always frustrated when [...] 12 | 13 | **Describe the solution you'd like** 14 | A clear and concise description of what you want to happen. 15 | 16 | **Describe alternatives you've considered** 17 | A clear and concise description of any alternative solutions or features you've considered. 18 | 19 | **Additional context** 20 | Add any other context or screenshots about the feature request here. 21 | -------------------------------------------------------------------------------- /.github/workflows/codeql-analysis.yml: -------------------------------------------------------------------------------- 1 | name: "CodeQL" 2 | 3 | on: 4 | push: 5 | branches: [master] 6 | pull_request: 7 | # The branches below must be a subset of the branches above 8 | branches: [master] 9 | schedule: 10 | - cron: '0 9 * * 4' 11 | 12 | jobs: 13 | analyze: 14 | name: Analyze 15 | runs-on: ubuntu-latest 16 | 17 | strategy: 18 | fail-fast: false 19 | matrix: 20 | # Override automatic language detection by changing the below list 21 | # Supported options are ['csharp', 'cpp', 'go', 'java', 'javascript', 'python'] 22 | language: ['python'] 23 | # Learn more... 24 | # https://docs.github.com/en/github/finding-security-vulnerabilities-and-errors-in-your-code/configuring-code-scanning#overriding-automatic-language-detection 25 | 26 | steps: 27 | - name: Checkout repository 28 | uses: actions/checkout@v2 29 | with: 30 | # We must fetch at least the immediate parents so that if this is 31 | # a pull request then we can checkout the head. 32 | fetch-depth: 2 33 | 34 | # If this run was triggered by a pull request event, then checkout 35 | # the head of the pull request instead of the merge commit. 36 | - run: git checkout HEAD^2 37 | if: ${{ github.event_name == 'pull_request' }} 38 | 39 | # Initializes the CodeQL tools for scanning. 40 | - name: Initialize CodeQL 41 | uses: github/codeql-action/init@v2 42 | with: 43 | languages: ${{ matrix.language }} 44 | # If you wish to specify custom queries, you can do so here or in a config file. 45 | # By default, queries listed here will override any specified in a config file. 46 | # Prefix the list here with "+" to use these queries and those in the config file. 47 | # queries: ./path/to/local/query, your-org/your-repo/queries@main 48 | 49 | # Autobuild attempts to build any compiled languages (C/C++, C#, or Java). 50 | # If this step fails, then you should remove it and run the build manually (see below) 51 | - name: Autobuild 52 | uses: github/codeql-action/autobuild@v2 53 | 54 | # ℹ️ Command-line programs to run using the OS shell. 55 | # 📚 https://git.io/JvXDl 56 | 57 | # ✏️ If the Autobuild fails above, remove it and uncomment the following three lines 58 | # and modify them (or add more) to build your code if your project 59 | # uses a compiled language 60 | 61 | #- run: | 62 | # make bootstrap 63 | # make release 64 | 65 | - name: Perform CodeQL Analysis 66 | uses: github/codeql-action/analyze@v2 67 | -------------------------------------------------------------------------------- /.github/workflows/pythonpackage.yml: -------------------------------------------------------------------------------- 1 | name: Python package 2 | 3 | on: [push] 4 | 5 | jobs: 6 | build: 7 | 8 | runs-on: windows-latest 9 | strategy: 10 | matrix: 11 | python-version: [3.8, 3.9] 12 | 13 | steps: 14 | - uses: actions/checkout@v2 15 | - name: Set up Python ${{ matrix.python-version }} 16 | uses: actions/setup-python@v1 17 | with: 18 | python-version: ${{ matrix.python-version }} 19 | - name: Install dependencies 20 | run: | 21 | python -m pip install --upgrade pip 22 | pip install -r requirements.txt 23 | - name: Lint with flake8 24 | run: | 25 | pip install flake8 26 | # stop the build if there are Python syntax errors or undefined names 27 | flake8 . --count --select=E9,F63,F7,F82 --show-source --statistics 28 | # exit-zero treats all errors as warnings. The GitHub editor is 127 chars wide 29 | flake8 . --count --exit-zero --max-complexity=10 --max-line-length=127 --statistics 30 | - name: Test with pytest 31 | run: | 32 | pip install pytest 33 | pytest 34 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2018 Sayar Mendis 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | [![forthebadge made-with-python](http://ForTheBadge.com/images/badges/made-with-python.svg)](https://www.python.org/) 2 | 3 | **Selenium Screenshot:** 4 | 5 | [![Open Source Love svg1](https://badges.frapsoft.com/os/v1/open-source.svg?v=103)](https://github.com/ellerbrock/open-source-badges/) 6 | [![License: MIT](https://img.shields.io/badge/License-MIT-yellow.svg)](https://opensource.org/licenses/MIT) 7 | [![Downloads](https://static.pepy.tech/personalized-badge/selenium-screenshot?period=total&units=none&left_color=yellowgreen&right_color=blue&left_text=Downloads)](https://pepy.tech/project/selenium-screenshot) 8 | [![Python 3.6](https://img.shields.io/badge/python-3.6-blue.svg)](https://www.python.org/downloads/release/python-360/) 9 | [![PyPI version](https://badge.fury.io/py/Selenium-Screenshot.svg)](https://badge.fury.io/py/Selenium-Screenshot) 10 | ![Python package](https://github.com/sam4u3/Selenium_Screenshot/workflows/Python%20package/badge.svg) 11 | 12 | 13 | The Selenium Screenshot is used to clip Html pages and elements using Selenium. 14 | 15 | **Installation:** 16 | 17 | `pip install Selenium-Screenshot` 18 | 19 | This package supports Python 3.6+ only. 20 | 21 | **How to Use:** 22 | 23 | **For Full Page Screenshot:** 24 | 25 | ```python 26 | from selenium import webdriver 27 | from Screenshot import Screenshot 28 | 29 | driver = webdriver.Chrome() 30 | driver.get("https://en.wikipedia.org/wiki/Python") 31 | 32 | ss = Screenshot(driver) 33 | 34 | # Example: hide sticky header + specific table 35 | table_to_hide = driver.find_element("css selector", "#p-search") 36 | 37 | ss.capture_full_page( 38 | output_path="python_wiki.png", 39 | hide_selectors=[".vector-sticky-header", "#mw-head", table_to_hide] # mix of CSS + WebElement 40 | ) 41 | 42 | driver.quit() 43 | ``` 44 | 45 | **For Html Element Clipping:** 46 | 47 | ````python 48 | from selenium import webdriver 49 | from Screenshot import Screenshot 50 | 51 | driver = webdriver.Chrome() 52 | driver.get("https://en.wikipedia.org/wiki/Python") 53 | 54 | ss = Screenshot(driver) 55 | 56 | # Capture a specific box (e.g., infobox) 57 | content_only = driver.find_element("css selector", "#mw-content-text") 58 | ss.capture_element(content_only, "content_only.png") 59 | 60 | driver.quit() 61 | 62 | ```` 63 | 64 | **Contact Information:** 65 | 66 | [Email:py.wizard.org@gmail.com](mailto::py.wizard.org@gmail.com) 67 | 68 | **Donation:** 69 | 70 | If you have found my software to be of any use to you, do consider helping me pay my internet bills. This would encourage me to maintain and create more projects. 71 | 72 | Donate via PayPal 73 | -------------------------------------------------------------------------------- /Screenshot/Screenshot.py: -------------------------------------------------------------------------------- 1 | import time 2 | import base64 3 | from io import BytesIO 4 | from PIL import Image 5 | from selenium import webdriver 6 | from selenium.webdriver.remote.webelement import WebElement 7 | 8 | class Screenshot: 9 | def __init__(self, driver: webdriver.Chrome, scroll_pause: float = 0.3): 10 | self.driver = driver 11 | self.scroll_pause = scroll_pause 12 | 13 | def hide_elements(self, elements_or_selectors): 14 | """Hide elements via CSS selector or WebElement objects.""" 15 | if not elements_or_selectors: 16 | return 17 | 18 | for item in elements_or_selectors: 19 | if isinstance(item, str): 20 | # Hide by CSS selector 21 | self.driver.execute_script(f""" 22 | document.querySelectorAll("{item}").forEach(el => {{ 23 | el.style.display = "none"; 24 | }}); 25 | """) 26 | elif isinstance(item, WebElement): 27 | # Hide specific element directly 28 | self.driver.execute_script(""" 29 | arguments[0].style.display = "none"; 30 | """, item) 31 | 32 | def _scroll_to_bottom(self): 33 | last_height = 0 34 | while True: 35 | self.driver.execute_script("window.scrollTo(0, document.body.scrollHeight);") 36 | time.sleep(self.scroll_pause) 37 | new_height = self.driver.execute_script("return document.body.scrollHeight") 38 | if new_height == last_height: 39 | break 40 | last_height = new_height 41 | 42 | def _get_page_dimensions(self): 43 | width = self.driver.execute_script("return Math.max(document.body.scrollWidth, document.documentElement.scrollWidth);") 44 | height = self.driver.execute_script("return Math.max(document.body.scrollHeight, document.documentElement.scrollHeight);") 45 | return width, height 46 | 47 | def capture_full_page(self, output_path="screenshot.png", hide_selectors=None): 48 | if hide_selectors is None: 49 | hide_selectors = ["#mw-head", "#mw-panel", ".vector-sticky-header"] 50 | self.hide_elements(hide_selectors) 51 | self._scroll_to_bottom() 52 | 53 | width, height = self._get_page_dimensions() 54 | 55 | # Force the viewport size using Chrome DevTools Protocol 56 | self.driver.execute_cdp_cmd("Emulation.setDeviceMetricsOverride", { 57 | "width": width, 58 | "height": height, 59 | "deviceScaleFactor": 1, 60 | "mobile": False 61 | }) 62 | time.sleep(0.5) 63 | 64 | # Take screenshot using CDP 65 | screenshot_data = self.driver.execute_cdp_cmd("Page.captureScreenshot", { 66 | "format": "png", 67 | "fromSurface": True, 68 | "captureBeyondViewport": True 69 | }) 70 | 71 | # Decode and save 72 | image_data = base64.b64decode(screenshot_data['data']) 73 | image = Image.open(BytesIO(image_data)) 74 | image.save(output_path) 75 | 76 | print(f"[✓] Full-page screenshot saved to: {output_path}") 77 | 78 | def capture_element(self, element: WebElement, output_path="element.png", padding=0): 79 | """Capture full-width screenshot of a specific element using full-page technique.""" 80 | # Ensure lazy content is loaded 81 | self.driver.execute_script("arguments[0].scrollIntoView({block: 'center'});", element) 82 | time.sleep(self.scroll_pause) 83 | 84 | # Get page size and set viewport to full page (CDP method) 85 | total_width = self.driver.execute_script( 86 | "return Math.max(document.body.scrollWidth, document.documentElement.scrollWidth);") 87 | total_height = self.driver.execute_script( 88 | "return Math.max(document.body.scrollHeight, document.documentElement.scrollHeight);") 89 | 90 | self.driver.execute_cdp_cmd("Emulation.setDeviceMetricsOverride", { 91 | "width": total_width, 92 | "height": total_height, 93 | "deviceScaleFactor": 1, 94 | "mobile": False 95 | }) 96 | time.sleep(0.5) 97 | 98 | # Take full screenshot using CDP 99 | screenshot_data = self.driver.execute_cdp_cmd("Page.captureScreenshot", { 100 | "format": "png", 101 | "fromSurface": True, 102 | "captureBeyondViewport": True 103 | }) 104 | png_data = base64.b64decode(screenshot_data['data']) 105 | image = Image.open(BytesIO(png_data)) 106 | 107 | # Get element position 108 | location = element.location_once_scrolled_into_view 109 | size = element.size 110 | 111 | left = int(location['x']) - padding 112 | top = int(location['y']) - padding 113 | right = int(location['x'] + size['width']) + padding 114 | bottom = int(location['y'] + size['height']) + padding 115 | 116 | cropped = image.crop((left, top, right, bottom)) 117 | cropped.save(output_path) 118 | print(f"[✓] Element screenshot saved to: {output_path}") -------------------------------------------------------------------------------- /Screenshot/__init__.py: -------------------------------------------------------------------------------- 1 | from .Screenshot import Screenshot 2 | -------------------------------------------------------------------------------- /Screenshot/__pycache__/Screenshot_Clipping.cpython-36.pyc: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/PyWizards/Selenium_Screenshot/10d95c8858d93d56035df69f7e52bf9824eb91e4/Screenshot/__pycache__/Screenshot_Clipping.cpython-36.pyc -------------------------------------------------------------------------------- /Screenshot/__pycache__/__init__.cpython-36.pyc: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/PyWizards/Selenium_Screenshot/10d95c8858d93d56035df69f7e52bf9824eb91e4/Screenshot/__pycache__/__init__.cpython-36.pyc -------------------------------------------------------------------------------- /examples/capture_element.py: -------------------------------------------------------------------------------- 1 | from selenium import webdriver 2 | from Screenshot import Screenshot 3 | 4 | driver = webdriver.Chrome() 5 | driver.get("https://en.wikipedia.org/wiki/Python") 6 | 7 | ss = Screenshot(driver) 8 | 9 | # Capture a specific box (e.g., infobox) 10 | content_only = driver.find_element("css selector", "#mw-content-text") 11 | ss.capture_element(content_only, "content_only.png") 12 | 13 | driver.quit() -------------------------------------------------------------------------------- /examples/capture_full_page.py: -------------------------------------------------------------------------------- 1 | from selenium import webdriver 2 | from Screenshot import Screenshot 3 | 4 | driver = webdriver.Chrome() 5 | driver.get("https://en.wikipedia.org/wiki/Python") 6 | 7 | ss = Screenshot(driver) 8 | 9 | # Example: hide sticky header + specific table 10 | table_to_hide = driver.find_element("css selector", "#p-search") 11 | 12 | ss.capture_full_page( 13 | output_path="python_wiki.png", 14 | hide_selectors=[".vector-sticky-header", "#mw-head", table_to_hide] # mix of CSS + WebElement 15 | ) 16 | 17 | driver.quit() -------------------------------------------------------------------------------- /requirements.txt: -------------------------------------------------------------------------------- 1 | selenium 2 | pillow 3 | -------------------------------------------------------------------------------- /setup.py: -------------------------------------------------------------------------------- 1 | import os 2 | 3 | from setuptools import find_packages, setup 4 | 5 | # The directory containing this file 6 | HERE = os.path.abspath(os.path.dirname(__file__)) 7 | 8 | # The text of the README file 9 | with open(os.path.join(HERE, "README.md")) as fid: 10 | README = fid.read() 11 | 12 | 13 | # This call to setup() does all the work 14 | setup( 15 | name="Selenium-Screenshot", 16 | version="3.0.0", 17 | description="This package is used to Clipped Images of Html Elements of Selenium Webdriver", 18 | long_description=README, 19 | long_description_content_type="text/markdown", 20 | url="https://github.com/PyWizards/Selenium_Screenshot", 21 | author="PyWizard org", 22 | author_email="py.wizard.org@gmail.com", 23 | license="MIT", 24 | platforms="any", 25 | python_requires=">=3.6.1", 26 | classifiers=[ 27 | "License :: OSI Approved :: MIT License", 28 | "Programming Language :: Python :: 3", 29 | "Programming Language :: Python :: 3.8", 30 | ], 31 | include_package_data=True, 32 | install_requires=["Pillow", "selenium"], 33 | packages=find_packages(exclude=("tests",)), 34 | 35 | ) 36 | -------------------------------------------------------------------------------- /test/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/PyWizards/Selenium_Screenshot/10d95c8858d93d56035df69f7e52bf9824eb91e4/test/__init__.py -------------------------------------------------------------------------------- /test/__pycache__/__init__.cpython-36.pyc: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/PyWizards/Selenium_Screenshot/10d95c8858d93d56035df69f7e52bf9824eb91e4/test/__pycache__/__init__.cpython-36.pyc -------------------------------------------------------------------------------- /test/__pycache__/test_IEdriver.cpython-36-PYTEST.pyc: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/PyWizards/Selenium_Screenshot/10d95c8858d93d56035df69f7e52bf9824eb91e4/test/__pycache__/test_IEdriver.cpython-36-PYTEST.pyc -------------------------------------------------------------------------------- /test/__pycache__/test_chrome_driver.cpython-36-PYTEST.pyc: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/PyWizards/Selenium_Screenshot/10d95c8858d93d56035df69f7e52bf9824eb91e4/test/__pycache__/test_chrome_driver.cpython-36-PYTEST.pyc -------------------------------------------------------------------------------- /test/test_screenshot.py: -------------------------------------------------------------------------------- 1 | import os 2 | import pytest 3 | from selenium import webdriver 4 | from Screenshot import Screenshot 5 | 6 | TEST_URL = "https://en.wikipedia.org/wiki/Python_(programming_language)" 7 | 8 | 9 | @pytest.fixture(scope="module") 10 | def driver(): 11 | options = webdriver.ChromeOptions() 12 | options.add_argument("--headless=new") 13 | options.add_argument("--disable-gpu") 14 | driver = webdriver.Chrome(options=options) 15 | driver.get(TEST_URL) 16 | yield driver 17 | driver.quit() 18 | 19 | 20 | def test_full_page_screenshot(driver, tmp_path): 21 | output_file = tmp_path / "full_page.png" 22 | ss = Screenshot(driver) 23 | ss.capture_full_page(str(output_file), hide_selectors=[".vector-sticky-header", "#mw-head"]) 24 | 25 | assert output_file.exists() 26 | assert os.path.getsize(output_file) > 10_000 # ensure it's not empty 27 | 28 | 29 | def test_element_screenshot(driver, tmp_path): 30 | output_file = tmp_path / "element.png" 31 | ss = Screenshot(driver) 32 | 33 | infobox = driver.find_element("css selector", ".infobox") 34 | ss.capture_element(infobox, str(output_file), padding=5) 35 | 36 | assert output_file.exists() 37 | assert os.path.getsize(output_file) > 5_000 # basic size check --------------------------------------------------------------------------------