Skip to content

Commit

Permalink
Implemented unit tests and configured CI/CD workflow & post-installat…
Browse files Browse the repository at this point in the history
…ion checks
  • Loading branch information
ishan-surana committed Aug 15, 2024
1 parent 0742021 commit e3ce551
Show file tree
Hide file tree
Showing 13 changed files with 173 additions and 21 deletions.
12 changes: 3 additions & 9 deletions .github/workflows/python-publish.yml
Original file line number Diff line number Diff line change
@@ -1,11 +1,3 @@
# This workflow will upload a Python Package using Twine when a release is created
# For more information see: https://docs.github.com/en/actions/automating-builds-and-tests/building-and-testing-python#publishing-to-package-registries

# This workflow uses actions that are not certified by GitHub.
# They are provided by a third-party and are governed by
# separate terms of service, privacy policy, and support
# documentation.

name: Upload Python Package

on:
Expand All @@ -30,7 +22,9 @@ jobs:
- name: Install dependencies
run: |
python -m pip install --upgrade pip
pip install build
python -m pip install build pytest pytest-mock selenium webdriver-manager
- name: Run unit tests
run: python -m pytest tests/ --maxfail=1 --disable-warnings -q
- name: Build package
run: python -m build
- name: Publish package
Expand Down
10 changes: 6 additions & 4 deletions MetaDataScraper.egg-info/PKG-INFO
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
Metadata-Version: 2.1
Name: MetaDataScraper
Version: 1.0.3
Version: 1.0.4
Summary: A module designed to automate the extraction of follower counts and post details from a public Facebook page.
Author-email: Ishan Surana <ishansurana1234@gmail.com>
Maintainer-email: Ishan Surana <ishansurana1234@gmail.com>
Expand All @@ -16,10 +16,12 @@ Classifier: Operating System :: Microsoft :: Windows
Requires-Python: >=3.10
Description-Content-Type: text/markdown
License-File: LICENCE
Requires-Dist: selenium==4.1.0
Requires-Dist: webdriver-manager==4.0.1
Requires-Dist: selenium==4.23.0
Requires-Dist: webdriver-manager==4.0.2

[![Licence](https://badgen.net/github/license/ishan-surana/MetaDataScraper?color=DC143C)](https://github.com/ishan-surana/MetaDataScraper/blob/main/LICENCE) [![Python](https://img.shields.io/badge/python-%3E=3.10-slateblue.svg)](https://www.python.org/downloads/release/python-3119/) [![Wheel](https://img.shields.io/badge/wheel-yes-FF00C9.svg)](https://files.pythonhosted.org/packages/02/80/c53d5e8439361c913e23b6345e85e748a7ac7e82e22cb9f7cd9ec77d5d52/MetaDataScraper-1.0.0-py3-none-any.whl) [![Latest](https://badgen.net/github/release/ishan-surana/MetaDataScraper?label=latest+release&color=green)](https://pypi.org/project/MetaDataScraper/1.0.0/) [![Releases](https://badgen.net/github/releases/ishan-surana/MetaDataScraper?color=orange)](https://github.com/ishan-surana/MetaDataScraper/releases) [![Stars](https://badgen.net/github/stars/ishan-surana/MetaDataScraper?color=yellow)](https://github.com/ishan-surana/MetaDataScraper/stargazers) [![Forks](https://badgen.net/github/forks/ishan-surana/MetaDataScraper?color=dark)](https://github.com/ishan-surana/MetaDataScraper/forks) [![Issues](https://badgen.net/github/issues/ishan-surana/MetaDataScraper?color=800000)](https://github.com/ishan-surana/MetaDataScraper/issues) [![PRs](https://badgen.net/github/prs/ishan-surana/MetaDataScraper?color=C71585)](https://github.com/ishan-surana/MetaDataScraper/pulls) [![Last commit](https://badgen.net/github/last-commit/ishan-surana/MetaDataScraper?color=blue)](https://github.com/ishan-surana/MetaDataScraper/commits/main/) ![Downloads](https://img.shields.io/github/downloads/ishan-surana/MetaDataScraper/total) [![Workflow](https://github.com/ishan-surana/MetaDataScraper/actions/workflows/python-publish.yml/badge.svg)](https://github.com/ishan-surana/MetaDataScraper/blob/main/.github/workflows/python-publish.yml) [![PyPI](https://d25lcipzij17d.cloudfront.net/badge.svg?id=py&r=r&ts=1683906897&type=6e&v=1.0.0&x2=0)](https://pypi.org/project/MetaDataScraper/) [![Maintained](https://img.shields.io/badge/maintained-yes-cyan)](https://github.com/ishan-surana/MetaDataScraper/pulse) [![OS](https://img.shields.io/badge/OS-Windows-FF0000)](https://www.microsoft.com/software-download/windows11) [![Documentation Status](https://readthedocs.org/projects/metadatascraper/badge/?version=latest)](https://metadatascraper.readthedocs.io/en/latest/?badge=latest)
[![Licence](https://badgen.net/github/license/ishan-surana/MetaDataScraper?color=DC143C)](https://github.com/ishan-surana/MetaDataScraper/blob/main/LICENCE) [![Python](https://img.shields.io/badge/python-%3E=3.10-slateblue.svg)](https://www.python.org/downloads/release/python-3119/) [![Wheel](https://img.shields.io/badge/wheel-yes-FF00C9.svg)](https://files.pythonhosted.org/packages/02/80/c53d5e8439361c913e23b6345e85e748a7ac7e82e22cb9f7cd9ec77d5d52/MetaDataScraper-1.0.0-py3-none-any.whl) [![Latest](https://badgen.net/github/release/ishan-surana/MetaDataScraper?label=latest+release&color=green)](https://pypi.org/project/MetaDataScraper/1.0.0/) [![Releases](https://badgen.net/github/releases/ishan-surana/MetaDataScraper?color=orange)](https://github.com/ishan-surana/MetaDataScraper/releases) [![Stars](https://badgen.net/github/stars/ishan-surana/MetaDataScraper?color=yellow)](https://github.com/ishan-surana/MetaDataScraper/stargazers) [![Forks](https://badgen.net/github/forks/ishan-surana/MetaDataScraper?color=dark)](https://github.com/ishan-surana/MetaDataScraper/forks) [![Issues](https://badgen.net/github/issues/ishan-surana/MetaDataScraper?color=800000)](https://github.com/ishan-surana/MetaDataScraper/issues) [![PRs](https://badgen.net/github/prs/ishan-surana/MetaDataScraper?color=C71585)](https://github.com/ishan-surana/MetaDataScraper/pulls) ![Downloads](https://img.shields.io/github/downloads/ishan-surana/MetaDataScraper/total) [![Last commit](https://badgen.net/github/last-commit/ishan-surana/MetaDataScraper?color=blue)](https://github.com/ishan-surana/MetaDataScraper/commits/main/) [![Workflow](https://github.com/ishan-surana/MetaDataScraper/actions/workflows/python-publish.yml/badge.svg)](https://github.com/ishan-surana/MetaDataScraper/blob/main/.github/workflows/python-publish.yml) [![PyPI](https://d25lcipzij17d.cloudfront.net/badge.svg?id=py&r=r&ts=1683906897&type=6e&v=1.0.0&x2=0)](https://pypi.org/project/MetaDataScraper/) [![Maintained](https://img.shields.io/badge/maintained-yes-cyan)](https://github.com/ishan-surana/MetaDataScraper/pulse) [![OS](https://img.shields.io/badge/OS-Windows-FF0000)](https://www.microsoft.com/software-download/windows11) [![Documentation Status](https://readthedocs.org/projects/metadatascraper/badge/?version=latest)](https://metadatascraper.readthedocs.io/en/latest/?badge=latest)<br>
---
## <div align=center>Support this package by donating here! ➡️ [![Buy Me a Coffee](https://img.shields.io/badge/Buy%20Me%20a%20Coffee-badge?style=plastic&logo=buy-me-a-coffee&color=black)](https://www.buymeacoffee.com/ishansurana) [![Paypal](https://img.shields.io/badge/PayPal-badge?style=plastic&logo=paypal&color=white)](https://www.paypal.com/paypalme/ishansurana)</div><br>

# MetaDataScraper

Expand Down
5 changes: 4 additions & 1 deletion MetaDataScraper.egg-info/SOURCES.txt
Original file line number Diff line number Diff line change
@@ -1,10 +1,13 @@
LICENCE
README.md
pyproject.toml
setup.py
MetaDataScraper/FacebookScraper.py
MetaDataScraper/__init__.py
MetaDataScraper.egg-info/PKG-INFO
MetaDataScraper.egg-info/SOURCES.txt
MetaDataScraper.egg-info/dependency_links.txt
MetaDataScraper.egg-info/requires.txt
MetaDataScraper.egg-info/top_level.txt
MetaDataScraper.egg-info/top_level.txt
tests/test_logged_in_scraper.py
tests/test_loginless_scraper.py
4 changes: 2 additions & 2 deletions MetaDataScraper.egg-info/requires.txt
Original file line number Diff line number Diff line change
@@ -1,2 +1,2 @@
selenium==4.1.0
webdriver-manager==4.0.1
selenium==4.23.0
webdriver-manager==4.0.2
2 changes: 1 addition & 1 deletion MetaDataScraper/FacebookScraper.py
Original file line number Diff line number Diff line change
Expand Up @@ -413,6 +413,7 @@ def __init__(self, page_id: str, email: str, password: str):
self.post_shares = []
self.is_video = []
self.video_links = []
self._logged_in = False

def __setup_driver(self):
"""Sets up the Selenium WebDriver with necessary options."""
Expand All @@ -427,7 +428,6 @@ def __setup_driver(self):

def __login(self):
"""Logs into Facebook using the provided credentials."""
self._logged_in = False
if self.driver.find_elements(By.ID, 'not_me_link'):
self.driver.find_element(By.ID, 'not_me_link').click()
self.driver.get('https://www.facebook.com/login')
Expand Down
Binary file removed dist/MetaDataScraper-1.0.3-py3-none-any.whl
Binary file not shown.
Binary file added dist/MetaDataScraper-1.0.4-py3-none-any.whl
Binary file not shown.
Binary file removed dist/metadatascraper-1.0.3.tar.gz
Binary file not shown.
Binary file added dist/metadatascraper-1.0.4.tar.gz
Binary file not shown.
8 changes: 4 additions & 4 deletions pyproject.toml
Original file line number Diff line number Diff line change
@@ -1,10 +1,10 @@
[build-system]
requires = ["setuptools>=61.0.0", "wheel"]
requires = ["wheel", "setuptools>=61.0.0", "pytest==8.3.2", "pytest-mock==3.14.0", "selenium==4.23.0", "webdriver-manager==4.0.2"]
build-backend = "setuptools.build_meta"

[project]
name = "MetaDataScraper"
version = "1.0.3"
version = "1.0.4"
authors = [
{ name="Ishan Surana", email="ishansurana1234@gmail.com" },
]
Expand All @@ -19,8 +19,8 @@ classifiers = [
"Operating System :: Microsoft :: Windows",
]
dependencies = [
'selenium == 4.1.0',
'webdriver-manager == 4.0.1'
'selenium==4.23.0',
'webdriver-manager==4.0.2'
]
requires-python = ">=3.10"
keywords = ["facebook", "scraper", "meta", "selenium", "webdriver-manager", "automation", "web-scraping", "web-crawling", "web-automation", "facebook-scraper", "facebook-web-scraper", "meta-scraper"]
Expand Down
11 changes: 11 additions & 0 deletions setup.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
import subprocess
import sys
from setuptools import setup
from setuptools.command.install import install

class PostInstallCommand(install):
def run(self):
install.run(self)
subprocess.check_call([sys.executable, '-m', 'pytest', 'tests'])

setup(cmdclass={'install': PostInstallCommand})
84 changes: 84 additions & 0 deletions tests/test_logged_in_scraper.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,84 @@
import unittest
from unittest.mock import patch, MagicMock
from MetaDataScraper.FacebookScraper import LoggedInScraper

class TestLoggedInScraper(unittest.TestCase):
def setUp(self):
self.page_id = "test_page_id"
self.email = "test@example.com"
self.password = "password"
self.scraper = LoggedInScraper(self.page_id, self.email, self.password)

@patch('MetaDataScraper.FacebookScraper.webdriver.Chrome')
@patch('MetaDataScraper.FacebookScraper.ChromeDriverManager')
def test_setup_driver(self, MockChromeDriverManager, MockChrome):
mock_service = MagicMock()
MockChromeDriverManager.return_value.install.return_value = mock_service
mock_driver = MagicMock()
MockChrome.return_value = mock_driver
self.scraper._LoggedInScraper__setup_driver()

MockChrome.assert_called_once()
MockChromeDriverManager.assert_called_once()
self.assertIsNotNone(self.scraper.driver)
self.assertEqual(self.scraper.driver, mock_driver)

@patch('MetaDataScraper.FacebookScraper.webdriver.Chrome')
@patch('MetaDataScraper.FacebookScraper.ChromeDriverManager')
def test_login(self, MockChromeDriverManager, MockChrome):
mock_service = MagicMock()
MockChromeDriverManager.return_value.install.return_value = mock_service
mock_driver = MagicMock()
MockChrome.return_value = mock_driver

self.scraper._LoggedInScraper__login = MagicMock()
self.scraper._LoggedInScraper__login.side_effect = None

try:
self.scraper._LoggedInScraper__setup_driver()
self.scraper._LoggedInScraper__login()
self.assertTrue(self.scraper._logged_in)
MockChrome.assert_called_once()
MockChromeDriverManager.assert_called_once()
self.assertIsNotNone(self.scraper.driver)
except Exception as e:
self.assertFalse(self.scraper._logged_in)

@patch.object(LoggedInScraper, '_LoggedInScraper__setup_driver')
@patch.object(LoggedInScraper, '_LoggedInScraper__login')
@patch.object(LoggedInScraper, '_LoggedInScraper__extract_followers_count')
@patch.object(LoggedInScraper, '_LoggedInScraper__extract_post_details')
def test_scrape(self, mock_extract_post_details, mock_extract_followers_count, mock_login, mock_setup_driver):
# Set up mocks
mock_login.return_value = None
mock_setup_driver.return_value = None
mock_extract_post_details.return_value = None
mock_extract_followers_count.return_value = None

mock_driver = MagicMock()
self.scraper.driver = mock_driver

result = self.scraper.scrape()

self.assertIsInstance(result, dict)
self.assertIn('followers', result)
self.assertIn('post_texts', result)
self.assertIn('post_likes', result)
self.assertIn('post_shares', result)
self.assertIn('is_video', result)
self.assertIn('video_links', result)

def test_initialization(self):
self.assertEqual(self.scraper.page_id, self.page_id)
self.assertEqual(self.scraper.email, self.email)
self.assertEqual(self.scraper.password, self.password)
self.assertIsNone(self.scraper.driver)
self.assertIsNone(self.scraper.followers)
self.assertEqual(self.scraper.post_texts, [])
self.assertEqual(self.scraper.post_likes, [])
self.assertEqual(self.scraper.post_shares, [])
self.assertEqual(self.scraper.is_video, [])
self.assertEqual(self.scraper.video_links, [])

if __name__ == '__main__':
unittest.main()
58 changes: 58 additions & 0 deletions tests/test_loginless_scraper.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,58 @@
import unittest
from unittest.mock import patch, MagicMock
from MetaDataScraper.FacebookScraper import LoginlessScraper

class TestLoginlessScraper(unittest.TestCase):
def setUp(self):
self.page_id = "test_page_id"
self.scraper = LoginlessScraper(self.page_id)

@patch.object(LoginlessScraper, '_LoginlessScraper__setup_driver')
@patch.object(LoginlessScraper, '_LoginlessScraper__navigate_to_page')
@patch.object(LoginlessScraper, '_LoginlessScraper__check_page_accessibility')
@patch.object(LoginlessScraper, '_LoginlessScraper__extract_followers_count')
@patch.object(LoginlessScraper, '_LoginlessScraper__scroll_to_top')
@patch.object(LoginlessScraper, '_LoginlessScraper__get_xpath_constructor')
@patch.object(LoginlessScraper, '_LoginlessScraper__extract_post_details')
def test_scrape(self, mock_extract_post_details, mock_get_xpath, mock_scroll, mock_extract_followers, mock_check_access, mock_navigate, mock_setup):
mock_extract_post_details.return_value = None
mock_get_xpath.return_value = None
mock_scroll.return_value = None
mock_extract_followers.return_value = None
mock_check_access.return_value = None
mock_navigate.return_value = None
mock_setup.return_value = None

result = self.scraper.scrape()

self.assertIsInstance(result, dict)
self.assertIn('followers', result)
self.assertIn('post_texts', result)
self.assertIn('post_likes', result)
self.assertIn('post_shares', result)
self.assertIn('is_video', result)
self.assertIn('video_links', result)

@patch('MetaDataScraper.FacebookScraper.webdriver.Chrome')
@patch('MetaDataScraper.FacebookScraper.ChromeDriverManager')
def test_setup_driver(self, MockChromeDriverManager, MockChrome):
mock_service = MagicMock()
MockChromeDriverManager.return_value.install.return_value = mock_service
self.scraper._LoginlessScraper__setup_driver()

MockChrome.assert_called_once()
MockChromeDriverManager.assert_called_once()
self.assertIsNotNone(self.scraper.driver)

def test_initialization(self):
self.assertEqual(self.scraper.page_id, self.page_id)
self.assertIsNone(self.scraper.driver)
self.assertIsNone(self.scraper.followers)
self.assertEqual(self.scraper.post_texts, [])
self.assertEqual(self.scraper.post_likes, [])
self.assertEqual(self.scraper.post_shares, [])
self.assertEqual(self.scraper.is_video, [])
self.assertEqual(self.scraper.video_links, [])

if __name__ == '__main__':
unittest.main()

0 comments on commit e3ce551

Please sign in to comment.