refactor(playwright_manager.py): 优化Playwright管理器代码

- 移除了未使用的`sys`导入。
- 改进了`headless`选项值的处理逻辑,使其更易读。
- 增强了日志记录信息的格式,使其更加清晰。
- 在创建浏览器上下文时增加了重试机制,并使用配置的超时时间。

feat(docker-entrypoint.sh): 添加对beat和flower服务类型的支持

- 当`SERVICE_TYPE`为`beat`时,启动Celery Beat服务。
- 当`SERVICE_TYPE`为`flower`时,启动Celery Flower监控服务。
- 更新了错误提示信息,以反映新的服务类型选项。

test(test_playwright_manager.py): 添加Playwright管理器的单元测试

- 新增了一个测试类`TestDistributedPlaywrightManager`。
- 包含一个测试方法`test_create_context`,用于验证上下文创建功能。
This commit is contained in:
danial
2025-09-18 12:16:47 +08:00
parent e0308edd0f
commit ccb2969663
3 changed files with 83 additions and 17 deletions

View File

@@ -7,7 +7,6 @@ Playwright分布式管理器
import asyncio
import os
import platform
import sys
import time
import uuid
from contextlib import asynccontextmanager
@@ -156,7 +155,12 @@ class DistributedPlaywrightManager:
if "headless" in launch_options:
headless_value = launch_options["headless"]
if isinstance(headless_value, str):
launch_options["headless"] = headless_value.lower() in ("true", "1", "yes", "on")
launch_options["headless"] = headless_value.lower() in (
"true",
"1",
"yes",
"on",
)
elif not isinstance(headless_value, bool):
launch_options["headless"] = bool(headless_value)
@@ -164,7 +168,7 @@ class DistributedPlaywrightManager:
# 启动Chromium浏览器
cls._browser = await cls._playwright.chromium.launch(**launch_options)
# 检查浏览器是否成功启动并连接
if not cls._browser.is_connected():
logger.error("浏览器启动后未连接")
@@ -184,7 +188,12 @@ class DistributedPlaywrightManager:
except Exception as e:
worker_id = cls._instance._worker_id if cls._instance else "unknown"
logger.error("初始化Playwright失败", error=str(e), worker_id=worker_id, exc_info=True)
logger.error(
"初始化Playwright失败",
error=str(e),
worker_id=worker_id,
exc_info=True,
)
return False
@classmethod
@@ -290,7 +299,7 @@ class DistributedPlaywrightManager:
try:
if not cls._browser:
raise RuntimeError("Browser实例不可用")
# 检查浏览器是否已连接
if not cls._browser.is_connected():
logger.warning("浏览器未连接,尝试重新初始化")
@@ -301,22 +310,56 @@ class DistributedPlaywrightManager:
raise RuntimeError("浏览器重新连接失败")
logger.info("创建新上下文")
# 为new_context调用添加超时控制
try:
context = await asyncio.wait_for(
cls._browser.new_context(),
timeout=30.0 # 30秒超时
)
except asyncio.TimeoutError:
logger.error("创建浏览器上下文超时")
raise RuntimeError("创建浏览器上下文超时")
# 使用配置的超时时间,增加重试逻辑
max_retries = 3
retry_delay = 2.0 # 初始重试延迟秒数
for attempt in range(max_retries):
try:
# 使用配置的Playwright超时时间
context = await asyncio.wait_for(
cls._browser.new_context(**context_options),
timeout=settings.PLAYWRIGHT_TIMEOUT,
)
break # 成功创建上下文,退出重试循环
except asyncio.TimeoutError:
if attempt == max_retries - 1:
logger.error("创建浏览器上下文超时,已达到最大重试次数")
raise RuntimeError("创建浏览器上下文超时")
logger.warning(
"创建浏览器上下文超时,准备重试",
attempt=attempt + 1,
max_retries=max_retries,
timeout=settings.PLAYWRIGHT_TIMEOUT,
retry_delay=retry_delay,
)
await asyncio.sleep(retry_delay)
retry_delay *= 2 # 指数退避
except Exception as e:
if attempt == max_retries - 1:
logger.error("创建浏览器上下文失败", error=str(e))
raise RuntimeError(f"创建浏览器上下文失败: {str(e)}")
logger.warning(
"创建浏览器上下文失败,准备重试",
error=str(e),
attempt=attempt + 1,
max_retries=max_retries,
retry_delay=retry_delay,
)
await asyncio.sleep(retry_delay)
retry_delay *= 2 # 指数退避
logger.info("设置默认超时")
# 设置默认超时
context.set_default_timeout(60000)
context.set_default_navigation_timeout(30000)
logger.info("应用上下文选项")
cls._contexts[context_id] = context

View File

@@ -58,7 +58,17 @@ elif [ "$SERVICE_TYPE" = "worker" ]; then
--without-gossip \
--without-mingle \
--without-heartbeat
elif [ "$SERVICE_TYPE" = "beat" ]; then
exec celery -A app.core.celery_app beat \
--loglevel=info \
--schedule=/tmp/celerybeat-schedule \
--pidfile=/tmp/celerybeat.pid
elif [ "$SERVICE_TYPE" = "flower" ]; then
exec celery -A app.core.celery_app flower \
--port=5555 \
--host=0.0.0.0 \
--loglevel=info
else
echo "SERVICE_TYPE must be 'api' or 'worker'"
echo "SERVICE_TYPE must be 'api', 'worker', 'beat', or 'flower'"
exit 1
fi

View File

@@ -0,0 +1,13 @@
import uuid
from unittest import IsolatedAsyncioTestCase
from app.core.playwright_manager import playwright_manager
class TestDistributedPlaywrightManager(IsolatedAsyncioTestCase):
async def test_create_context(self):
# 使用Playwright上下文管理器
async with playwright_manager.get_order_context(
uuid.uuid4().hex
) as context_info:
print("我成功创建了一个上下文")