Compare commits

..

2 Commits

5 changed files with 205 additions and 0 deletions

2
.gitignore vendored
View File

@@ -17,3 +17,5 @@ Scrapling/
# Packaging artifacts # Packaging artifacts
*.egg-info/ *.egg-info/
.DS_Store

View File

@@ -91,3 +91,17 @@ wscraper hf -a dtf -u "https://www.happyfappy.net/torrents.php?id=110178" -c coo
│ └── happyfappy.py │ └── happyfappy.py
└── README.md └── README.md
``` ```
## 6) E2E Test (Canli)
Bu testler gercek siteye istek atar ve link/selector kirilmalarini yakalamayi hedefler.
```bash
python -m pip install -e ".[test]"
WSCRAPER_E2E=1 pytest -m e2e -s -vv --color=yes
```
Opsiyonel degiskenler:
- `WSCRAPER_COOKIE_FILE` (varsayilan: `cookies.txt`)
- `WSCRAPER_TEST_TORRENT_URL` (varsayilan: `https://www.happyfappy.net/torrents.php?id=110178`)

View File

@@ -12,6 +12,11 @@ dependencies = [
"scrapling[fetchers]==0.4.1", "scrapling[fetchers]==0.4.1",
] ]
[project.optional-dependencies]
test = [
"pytest>=8.0",
]
[project.scripts] [project.scripts]
wscraper = "wscraper.cli:main" wscraper = "wscraper.cli:main"
@@ -20,3 +25,8 @@ package-dir = {"" = "src"}
[tool.setuptools.packages.find] [tool.setuptools.packages.find]
where = ["src"] where = ["src"]
[tool.pytest.ini_options]
markers = [
"e2e: live end-to-end tests against external services",
]

16
tests/conftest.py Normal file
View File

@@ -0,0 +1,16 @@
from __future__ import annotations
def pytest_terminal_summary(terminalreporter, exitstatus, config):
_ = (exitstatus, config)
passed = len(terminalreporter.stats.get("passed", []))
failed = len(terminalreporter.stats.get("failed", []))
skipped = len(terminalreporter.stats.get("skipped", []))
terminalreporter.write_sep("=", "E2E SUMMARY", cyan=True)
terminalreporter.write_line(f"✅ Passed : {passed}", green=True)
if failed:
terminalreporter.write_line(f"❌ Failed : {failed}", red=True)
else:
terminalreporter.write_line(f"❌ Failed : {failed}", green=True)
terminalreporter.write_line(f"⚠️ Skipped: {skipped}", yellow=True)

View File

@@ -0,0 +1,163 @@
from __future__ import annotations
import json
import os
import subprocess
import sys
import time
from pathlib import Path
import pytest
pytestmark = [pytest.mark.e2e]
def _e2e_enabled() -> bool:
return os.getenv("WSCRAPER_E2E", "").strip() == "1"
def _base_env() -> dict[str, str]:
env = os.environ.copy()
src_path = str(Path.cwd() / "src")
current_pythonpath = env.get("PYTHONPATH", "").strip()
env["PYTHONPATH"] = f"{src_path}{os.pathsep}{current_pythonpath}" if current_pythonpath else src_path
return env
def _log(tr, message: str, kind: str = "info") -> None:
icon = ""
style: dict[str, bool] = {}
if kind == "ok":
icon = ""
style = {"green": True}
elif kind == "err":
icon = ""
style = {"red": True}
elif kind == "warn":
icon = "⚠️"
style = {"yellow": True}
elif kind == "run":
icon = "🚀"
style = {"cyan": True}
if tr is not None:
tr.write_line(f"{icon} {message}", **style)
else:
print(f"{icon} {message}")
def _run_cli_live(args: list[str], tr, timeout: int = 900) -> tuple[int, str]:
cmd = [sys.executable, "-m", "wscraper"] + args
_log(tr, f"Running: {' '.join(cmd)}", kind="run")
started = time.time()
proc = subprocess.Popen(
cmd,
text=True,
stdout=subprocess.PIPE,
stderr=subprocess.STDOUT,
env=_base_env(),
)
output_lines: list[str] = []
assert proc.stdout is not None
for line in proc.stdout:
output_lines.append(line)
clean = line.rstrip("\n")
if clean:
if tr is not None:
tr.write_line(f" {clean}")
else:
print(f" {clean}")
return_code = proc.wait(timeout=timeout)
duration = time.time() - started
if return_code == 0:
_log(tr, f"Command finished successfully in {duration:.2f}s", kind="ok")
else:
_log(tr, f"Command failed with exit code {return_code} in {duration:.2f}s", kind="err")
return return_code, "".join(output_lines)
@pytest.fixture
def tr(request):
return request.config.pluginmanager.getplugin("terminalreporter")
@pytest.mark.skipif(not _e2e_enabled(), reason="Set WSCRAPER_E2E=1 to run live tests")
def test_get_bookmarks_live(tmp_path: Path, tr) -> None:
cookie_file = Path(os.getenv("WSCRAPER_COOKIE_FILE", "cookies.txt"))
if not cookie_file.exists():
pytest.skip(f"Cookie file not found: {cookie_file}")
output_file = tmp_path / "bookmarks.json"
_log(tr, f"Output file: {output_file}")
return_code, output_text = _run_cli_live(
[
"happyfappy",
"--action",
"get-bookmarks",
"-c",
str(cookie_file),
"-o",
str(output_file),
],
tr,
)
assert return_code == 0, f"CLI failed:\n{output_text}"
assert output_file.exists(), "bookmarks.json was not created"
data = json.loads(output_file.read_text(encoding="utf-8"))
assert isinstance(data, list), "bookmarks output must be a JSON list"
assert len(data) >= 1, "expected at least one bookmark record"
_log(tr, f"Extracted records: {len(data)}", kind="ok")
first = data[0]
assert isinstance(first, dict), "bookmark entry must be an object"
for required_key in ("pageURL", "isVR", "title", "backgroundImage"):
assert required_key in first, f"missing key: {required_key}"
assert isinstance(first["pageURL"], str) and first["pageURL"].startswith("http")
assert isinstance(first["isVR"], bool)
assert isinstance(first["title"], str) and first["title"].strip() != ""
@pytest.mark.skipif(not _e2e_enabled(), reason="Set WSCRAPER_E2E=1 to run live tests")
def test_download_torrent_file_live(tmp_path: Path, tr) -> None:
cookie_file = Path(os.getenv("WSCRAPER_COOKIE_FILE", "cookies.txt"))
if not cookie_file.exists():
pytest.skip(f"Cookie file not found: {cookie_file}")
test_url = os.getenv(
"WSCRAPER_TEST_TORRENT_URL",
"https://www.happyfappy.net/torrents.php?id=110178",
)
output_dir = tmp_path / "torrent"
_log(tr, f"Output dir: {output_dir}")
return_code, output_text = _run_cli_live(
[
"happyfappy",
"--action",
"download-torrent-files",
"-u",
test_url,
"-c",
str(cookie_file),
"-o",
str(output_dir),
],
tr,
)
assert return_code == 0, f"CLI failed:\n{output_text}"
assert output_dir.exists(), "torrent output directory was not created"
torrent_files = list(output_dir.glob("*.torrent"))
assert len(torrent_files) >= 1, "expected at least one .torrent file"
_log(tr, f"Downloaded .torrent files: {len(torrent_files)}", kind="ok")
content = torrent_files[0].read_bytes()
assert content.startswith(b"d"), "torrent file should start with bencode dictionary token 'd'"
assert b"4:info" in content[:4096], "torrent file should include 'info' dictionary marker"