#!/usr/bin/env python3 """ Frontend Link Crawler for LittleShop Admin Panel Tests all navigation links in both mobile and desktop views """ import requests import re from bs4 import BeautifulSoup from urllib.parse import urljoin, urlparse import json import time from collections import defaultdict class FrontendLinkCrawler: def __init__(self, base_url="http://localhost:5000", username="admin", password="admin"): self.base_url = base_url self.session = requests.Session() self.visited_urls = set() self.broken_links = [] self.redirect_links = [] self.navigation_links = {} self.test_results = { "mobile": {"working": [], "broken": [], "redirects": []}, "desktop": {"working": [], "broken": [], "redirects": []} } # Login credentials self.username = username self.password = password def login(self): """Login to the admin panel""" print("šŸ” Logging into admin panel...") # Get login page to get anti-forgery token login_url = f"{self.base_url}/Admin/Account/Login" response = self.session.get(login_url) if response.status_code != 200: print(f"āŒ Failed to access login page: {response.status_code}") return False soup = BeautifulSoup(response.content, 'html.parser') token_input = soup.find('input', {'name': '__RequestVerificationToken'}) if not token_input: print("āŒ Could not find anti-forgery token") return False token = token_input.get('value') # Submit login form login_data = { 'Username': self.username, 'Password': self.password, '__RequestVerificationToken': token } response = self.session.post(login_url, data=login_data, allow_redirects=True) # Check if login was successful (should redirect to dashboard) if '/Admin/Dashboard' in response.url or response.url.endswith('/Admin'): print("āœ… Successfully logged in") return True else: print(f"āŒ Login failed - redirected to: {response.url}") return False def get_page_links(self, url, view_type="desktop"): """Extract all links from a page""" headers = { 'User-Agent': 'Mozilla/5.0 (iPhone; CPU iPhone OS 14_0 like Mac OS X) AppleWebKit/605.1.15' if view_type == "mobile" else 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36' } try: response = self.session.get(url, headers=headers, timeout=10) response.raise_for_status() soup = BeautifulSoup(response.content, 'html.parser') links = [] # Extract all anchor tags for a_tag in soup.find_all('a', href=True): href = a_tag.get('href') if href: full_url = urljoin(url, href) link_text = a_tag.get_text(strip=True) link_classes = a_tag.get('class', []) # Only include admin panel links if '/Admin' in full_url: links.append({ 'url': full_url, 'text': link_text, 'classes': link_classes, 'source_page': url }) return links, response.status_code except requests.RequestException as e: print(f"āŒ Error accessing {url}: {str(e)}") return [], 0 def test_link(self, link_info, view_type="desktop"): """Test if a link is working""" url = link_info['url'] headers = { 'User-Agent': 'Mozilla/5.0 (iPhone; CPU iPhone OS 14_0 like Mac OS X) AppleWebKit/605.1.15' if view_type == "mobile" else 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36' } try: response = self.session.get(url, headers=headers, timeout=10, allow_redirects=False) result = { 'url': url, 'text': link_info['text'], 'source_page': link_info['source_page'], 'status_code': response.status_code, 'view_type': view_type } if response.status_code == 200: self.test_results[view_type]["working"].append(result) return "working" elif response.status_code in [301, 302, 303, 307, 308]: result['redirect_url'] = response.headers.get('Location', 'Unknown') self.test_results[view_type]["redirects"].append(result) return "redirect" else: self.test_results[view_type]["broken"].append(result) return "broken" except requests.RequestException as e: result = { 'url': url, 'text': link_info['text'], 'source_page': link_info['source_page'], 'error': str(e), 'view_type': view_type } self.test_results[view_type]["broken"].append(result) return "error" def crawl_admin_panel(self): """Main crawling function""" if not self.login(): return False print("šŸ•·ļø Starting frontend link crawler...") # Key admin pages to test admin_pages = [ f"{self.base_url}/Admin/Dashboard", f"{self.base_url}/Admin/Categories", f"{self.base_url}/Admin/Products", f"{self.base_url}/Admin/Orders", f"{self.base_url}/Admin/Users", f"{self.base_url}/Admin/Bots", f"{self.base_url}/Admin/Messages", f"{self.base_url}/Admin/ShippingRates" ] all_links = set() # Collect all links from each page for page_url in admin_pages: print(f"šŸ“„ Scanning page: {page_url}") # Get links for both mobile and desktop views for view_type in ["desktop", "mobile"]: links, status_code = self.get_page_links(page_url, view_type) if status_code == 200: print(f" āœ… Found {len(links)} links in {view_type} view") for link in links: link['view_type'] = view_type all_links.add((link['url'], link['text'], link['source_page'], view_type)) else: print(f" āŒ Failed to access {page_url} in {view_type} view: {status_code}") print(f"\nšŸ” Testing {len(all_links)} unique links...") # Test each unique link for url, text, source_page, view_type in all_links: link_info = { 'url': url, 'text': text, 'source_page': source_page } result = self.test_link(link_info, view_type) status_emoji = "āœ…" if result == "working" else "šŸ”„" if result == "redirect" else "āŒ" print(f" {status_emoji} [{view_type}] {text}: {url} -> {result}") # Small delay to avoid overwhelming the server time.sleep(0.1) def generate_report(self): """Generate a comprehensive test report""" report = { "timestamp": time.strftime("%Y-%m-%d %H:%M:%S"), "summary": { "mobile": { "working": len(self.test_results["mobile"]["working"]), "broken": len(self.test_results["mobile"]["broken"]), "redirects": len(self.test_results["mobile"]["redirects"]) }, "desktop": { "working": len(self.test_results["desktop"]["working"]), "broken": len(self.test_results["desktop"]["broken"]), "redirects": len(self.test_results["desktop"]["redirects"]) } }, "details": self.test_results } # Save detailed report with open('/mnt/c/Production/Source/LittleShop/frontend_test_report.json', 'w') as f: json.dump(report, f, indent=2) # Generate readable summary print("\n" + "="*80) print("šŸ” FRONTEND LINK CRAWLER REPORT") print("="*80) for view_type in ["mobile", "desktop"]: print(f"\nšŸ“± {view_type.upper()} VIEW RESULTS:") summary = report["summary"][view_type] print(f" āœ… Working Links: {summary['working']}") print(f" šŸ”„ Redirects: {summary['redirects']}") print(f" āŒ Broken Links: {summary['broken']}") # Show broken links in detail if self.test_results[view_type]["broken"]: print(f"\nāŒ BROKEN LINKS ({view_type.upper()}):") for broken in self.test_results[view_type]["broken"]: print(f" • {broken['text']}: {broken['url']}") print(f" Source: {broken['source_page']}") if 'error' in broken: print(f" Error: {broken['error']}") else: print(f" Status: {broken['status_code']}") print() # Show suspicious redirects if self.test_results[view_type]["redirects"]: print(f"\nšŸ”„ REDIRECTS ({view_type.upper()}):") for redirect in self.test_results[view_type]["redirects"]: print(f" • {redirect['text']}: {redirect['url']}") print(f" Redirects to: {redirect.get('redirect_url', 'Unknown')}") print(f" Source: {redirect['source_page']}") print() print("="*80) print(f"šŸ“Š Report saved to: frontend_test_report.json") return report def main(): crawler = FrontendLinkCrawler() crawler.crawl_admin_panel() report = crawler.generate_report() # Quick summary total_broken = sum(len(report["details"][view]["broken"]) for view in ["mobile", "desktop"]) if total_broken > 0: print(f"\nāš ļø Found {total_broken} broken links that need attention!") return 1 else: print(f"\nšŸŽ‰ All links are working correctly!") return 0 if __name__ == "__main__": exit(main())