nonebot-plugin-htmlrender 0.6.0__tar.gz → 0.6.1__tar.gz

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (57) hide show
  1. {nonebot_plugin_htmlrender-0.6.0 → nonebot_plugin_htmlrender-0.6.1}/PKG-INFO +1 -1
  2. {nonebot_plugin_htmlrender-0.6.0 → nonebot_plugin_htmlrender-0.6.1}/nonebot_plugin_htmlrender/__init__.py +4 -4
  3. {nonebot_plugin_htmlrender-0.6.0 → nonebot_plugin_htmlrender-0.6.1}/nonebot_plugin_htmlrender/browser.py +19 -16
  4. nonebot_plugin_htmlrender-0.6.1/nonebot_plugin_htmlrender/compat.py +24 -0
  5. {nonebot_plugin_htmlrender-0.6.0 → nonebot_plugin_htmlrender-0.6.1}/nonebot_plugin_htmlrender/config.py +24 -43
  6. {nonebot_plugin_htmlrender-0.6.0 → nonebot_plugin_htmlrender-0.6.1}/nonebot_plugin_htmlrender/consts.py +2 -2
  7. {nonebot_plugin_htmlrender-0.6.0 → nonebot_plugin_htmlrender-0.6.1}/nonebot_plugin_htmlrender/install.py +23 -14
  8. {nonebot_plugin_htmlrender-0.6.0 → nonebot_plugin_htmlrender-0.6.1}/pyproject.toml +1 -1
  9. {nonebot_plugin_htmlrender-0.6.0 → nonebot_plugin_htmlrender-0.6.1}/tests/test_browser.py +2 -45
  10. {nonebot_plugin_htmlrender-0.6.0 → nonebot_plugin_htmlrender-0.6.1}/tests/test_install.py +2 -2
  11. nonebot_plugin_htmlrender-0.6.1/uv.lock +1714 -0
  12. nonebot_plugin_htmlrender-0.6.0/tests/test_config_custom_validation.py +0 -120
  13. nonebot_plugin_htmlrender-0.6.0/uv.lock +0 -1673
  14. {nonebot_plugin_htmlrender-0.6.0 → nonebot_plugin_htmlrender-0.6.1}/.github/workflows/codecov.yml +0 -0
  15. {nonebot_plugin_htmlrender-0.6.0 → nonebot_plugin_htmlrender-0.6.1}/.github/workflows/publish.yml +0 -0
  16. {nonebot_plugin_htmlrender-0.6.0 → nonebot_plugin_htmlrender-0.6.1}/.gitignore +0 -0
  17. {nonebot_plugin_htmlrender-0.6.0 → nonebot_plugin_htmlrender-0.6.1}/.pre-commit-config.yaml +0 -0
  18. {nonebot_plugin_htmlrender-0.6.0 → nonebot_plugin_htmlrender-0.6.1}/.python-version +0 -0
  19. {nonebot_plugin_htmlrender-0.6.0 → nonebot_plugin_htmlrender-0.6.1}/LICENSE +0 -0
  20. {nonebot_plugin_htmlrender-0.6.0 → nonebot_plugin_htmlrender-0.6.1}/README.md +0 -0
  21. {nonebot_plugin_htmlrender-0.6.0 → nonebot_plugin_htmlrender-0.6.1}/docs/example.md +0 -0
  22. {nonebot_plugin_htmlrender-0.6.0 → nonebot_plugin_htmlrender-0.6.1}/docs/html2pic.png +0 -0
  23. {nonebot_plugin_htmlrender-0.6.0 → nonebot_plugin_htmlrender-0.6.1}/docs/md2pic.png +0 -0
  24. {nonebot_plugin_htmlrender-0.6.0 → nonebot_plugin_htmlrender-0.6.1}/docs/template2pic.png +0 -0
  25. {nonebot_plugin_htmlrender-0.6.0 → nonebot_plugin_htmlrender-0.6.1}/docs/text2pic.png +0 -0
  26. {nonebot_plugin_htmlrender-0.6.0 → nonebot_plugin_htmlrender-0.6.1}/example/.env.dev +0 -0
  27. {nonebot_plugin_htmlrender-0.6.0 → nonebot_plugin_htmlrender-0.6.1}/example/bot.py +0 -0
  28. {nonebot_plugin_htmlrender-0.6.0 → nonebot_plugin_htmlrender-0.6.1}/example/plugins/render/__init__.py +0 -0
  29. {nonebot_plugin_htmlrender-0.6.0 → nonebot_plugin_htmlrender-0.6.1}/example/plugins/render/html2pic.html +0 -0
  30. {nonebot_plugin_htmlrender-0.6.0 → nonebot_plugin_htmlrender-0.6.1}/example/plugins/render/templates/markdown.css +0 -0
  31. {nonebot_plugin_htmlrender-0.6.0 → nonebot_plugin_htmlrender-0.6.1}/example/plugins/render/templates/mystyle.css +0 -0
  32. {nonebot_plugin_htmlrender-0.6.0 → nonebot_plugin_htmlrender-0.6.1}/example/plugins/render/templates/progress.html.jinja2 +0 -0
  33. {nonebot_plugin_htmlrender-0.6.0 → nonebot_plugin_htmlrender-0.6.1}/example/plugins/render/templates/text.html +0 -0
  34. {nonebot_plugin_htmlrender-0.6.0 → nonebot_plugin_htmlrender-0.6.1}/example/plugins/render/utils.py +0 -0
  35. {nonebot_plugin_htmlrender-0.6.0 → nonebot_plugin_htmlrender-0.6.1}/nonebot_plugin_htmlrender/data_source.py +0 -0
  36. {nonebot_plugin_htmlrender-0.6.0 → nonebot_plugin_htmlrender-0.6.1}/nonebot_plugin_htmlrender/process.py +0 -0
  37. {nonebot_plugin_htmlrender-0.6.0 → nonebot_plugin_htmlrender-0.6.1}/nonebot_plugin_htmlrender/signal.py +0 -0
  38. {nonebot_plugin_htmlrender-0.6.0 → nonebot_plugin_htmlrender-0.6.1}/nonebot_plugin_htmlrender/templates/github-markdown-light.css +0 -0
  39. {nonebot_plugin_htmlrender-0.6.0 → nonebot_plugin_htmlrender-0.6.1}/nonebot_plugin_htmlrender/templates/katex/katex.min.b64_fonts.css +0 -0
  40. {nonebot_plugin_htmlrender-0.6.0 → nonebot_plugin_htmlrender-0.6.1}/nonebot_plugin_htmlrender/templates/katex/katex.min.js +0 -0
  41. {nonebot_plugin_htmlrender-0.6.0 → nonebot_plugin_htmlrender-0.6.1}/nonebot_plugin_htmlrender/templates/katex/mathtex-script-type.min.js +0 -0
  42. {nonebot_plugin_htmlrender-0.6.0 → nonebot_plugin_htmlrender-0.6.1}/nonebot_plugin_htmlrender/templates/katex/mhchem.min.js +0 -0
  43. {nonebot_plugin_htmlrender-0.6.0 → nonebot_plugin_htmlrender-0.6.1}/nonebot_plugin_htmlrender/templates/markdown.html +0 -0
  44. {nonebot_plugin_htmlrender-0.6.0 → nonebot_plugin_htmlrender-0.6.1}/nonebot_plugin_htmlrender/templates/pygments-default.css +0 -0
  45. {nonebot_plugin_htmlrender-0.6.0 → nonebot_plugin_htmlrender-0.6.1}/nonebot_plugin_htmlrender/templates/text.css +0 -0
  46. {nonebot_plugin_htmlrender-0.6.0 → nonebot_plugin_htmlrender-0.6.1}/nonebot_plugin_htmlrender/templates/text.html +0 -0
  47. {nonebot_plugin_htmlrender-0.6.0 → nonebot_plugin_htmlrender-0.6.1}/nonebot_plugin_htmlrender/utils.py +0 -0
  48. {nonebot_plugin_htmlrender-0.6.0 → nonebot_plugin_htmlrender-0.6.1}/tests/__init__.py +0 -0
  49. {nonebot_plugin_htmlrender-0.6.0 → nonebot_plugin_htmlrender-0.6.1}/tests/conftest.py +0 -0
  50. {nonebot_plugin_htmlrender-0.6.0 → nonebot_plugin_htmlrender-0.6.1}/tests/resources/test_template_filter.png +0 -0
  51. {nonebot_plugin_htmlrender-0.6.0 → nonebot_plugin_htmlrender-0.6.1}/tests/templates/markdown.css +0 -0
  52. {nonebot_plugin_htmlrender-0.6.0 → nonebot_plugin_htmlrender-0.6.1}/tests/templates/mystyle.css +0 -0
  53. {nonebot_plugin_htmlrender-0.6.0 → nonebot_plugin_htmlrender-0.6.1}/tests/templates/progress.html.jinja2 +0 -0
  54. {nonebot_plugin_htmlrender-0.6.0 → nonebot_plugin_htmlrender-0.6.1}/tests/templates/text.html +0 -0
  55. {nonebot_plugin_htmlrender-0.6.0 → nonebot_plugin_htmlrender-0.6.1}/tests/test_deprecated_decorator.py +0 -0
  56. {nonebot_plugin_htmlrender-0.6.0 → nonebot_plugin_htmlrender-0.6.1}/tests/test_htmlrender.py +0 -0
  57. {nonebot_plugin_htmlrender-0.6.0 → nonebot_plugin_htmlrender-0.6.1}/tests/test_process.py +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: nonebot-plugin-htmlrender
3
- Version: 0.6.0
3
+ Version: 0.6.1
4
4
  Summary: 通过浏览器渲染图片
5
5
  Project-URL: Homepage, https://github.com/kexue-z/nonebot-plugin-htmlrender
6
6
  Project-URL: Bug Tracker, https://github.com/kexue-z/nonebot-plugin-htmlrender/issues
@@ -30,16 +30,16 @@ driver = nonebot.get_driver()
30
30
 
31
31
  @driver.on_startup
32
32
  async def init(**kwargs):
33
- logger.info("htmlrender启动中...")
33
+ logger.info("HTMLRender Starting...")
34
34
  await init_browser(**kwargs)
35
- logger.info("htmlrender已启动.")
35
+ logger.info("HTMLRender Started.")
36
36
 
37
37
 
38
38
  @driver.on_shutdown
39
39
  async def shutdown():
40
- logger.info("htmlrender关闭中...")
40
+ logger.info("HTMLRender Shutting down...")
41
41
  await shutdown_browser()
42
- logger.info("htmlrender已关闭.")
42
+ logger.info("HTMLRender Shut down.")
43
43
 
44
44
 
45
45
  __all__ = [
@@ -6,13 +6,12 @@ from nonebot.log import logger
6
6
  from playwright.async_api import (
7
7
  Browser,
8
8
  BrowserType,
9
- Error,
10
9
  Page,
11
10
  Playwright,
12
11
  async_playwright,
13
12
  )
14
13
 
15
- from nonebot_plugin_htmlrender.config import BrowserEngineType, plugin_config
14
+ from nonebot_plugin_htmlrender.config import plugin_config
16
15
  from nonebot_plugin_htmlrender.install import install_browser
17
16
  from nonebot_plugin_htmlrender.utils import proxy_settings, suppress_and_log
18
17
 
@@ -20,12 +19,12 @@ _browser: Optional[Browser] = None
20
19
  _playwright: Optional[Playwright] = None
21
20
 
22
21
 
23
- async def _launch(browser_type: BrowserEngineType, **kwargs) -> Browser:
22
+ async def _launch(browser_type: str, **kwargs) -> Browser:
24
23
  """
25
24
  启动浏览器实例。
26
25
 
27
26
  Args:
28
- browser_type (BrowserEngineType): 浏览器类型。
27
+ browser_type (str): 浏览器类型。
29
28
  **kwargs: 传递给`playwright.launch`的关键字参数。
30
29
 
31
30
  Returns:
@@ -54,18 +53,9 @@ async def init_browser(**kwargs) -> Browser:
54
53
  Raises:
55
54
  RuntimeError: 如果浏览器无法启动或安装失败。
56
55
  """
57
- try:
58
- return await start_browser(**kwargs)
59
- except (Error, Exception) as e:
60
- if "Executable doesn't exist" not in str(e):
61
- raise RuntimeError("无法启动浏览器实例") from e
62
- try:
63
- if await install_browser():
64
- return await start_browser(**kwargs)
65
- else:
66
- raise RuntimeError("无法启动浏览器实例且尝试安装失败") from e
67
- except Exception as e:
68
- raise RuntimeError("无法启动浏览器实例") from e
56
+ await install_browser() # update playwright when start
57
+ await check_playwright_env()
58
+ return await start_browser(**kwargs)
69
59
 
70
60
 
71
61
  @asynccontextmanager
@@ -167,3 +157,16 @@ async def shutdown_browser() -> None:
167
157
  if _playwright:
168
158
  with suppress_and_log():
169
159
  await _playwright.stop()
160
+
161
+
162
+ async def check_playwright_env():
163
+ """Check Playwright environment."""
164
+ logger.info("Checking Playwright environment...")
165
+ try:
166
+ async with async_playwright() as p:
167
+ await p.chromium.launch()
168
+ except Exception as e:
169
+ raise RuntimeError(
170
+ "Playwright environment is not set up correctly. "
171
+ "Refer to https://playwright.dev/python/docs/intro#system-requirements"
172
+ ) from e
@@ -0,0 +1,24 @@
1
+ from typing import Literal, overload
2
+
3
+ from nonebot.compat import PYDANTIC_V2
4
+
5
+ __all__ = ("model_validator",)
6
+
7
+ # todo)) 等nb更新切换到nb提供的兼容层
8
+ if PYDANTIC_V2:
9
+ from pydantic import field_validator as field_validator
10
+ from pydantic import model_validator as model_validator
11
+ else:
12
+ from pydantic import root_validator, validator
13
+
14
+ @overload
15
+ def model_validator(*, mode: Literal["before"]): ...
16
+
17
+ @overload
18
+ def model_validator(*, mode: Literal["after"]): ...
19
+
20
+ def model_validator(*, mode: Literal["before", "after"] = "after"):
21
+ return root_validator(pre=mode == "before", allow_reuse=True)
22
+
23
+ def field_validator(__field, *fields, mode: Literal["before", "after"] = "after"): # noqa: PYI063
24
+ return validator(__field, *fields, pre=mode == "before", allow_reuse=True)
@@ -1,55 +1,17 @@
1
- from collections.abc import Generator
2
- from typing import Any, Callable, ClassVar, Optional
1
+ from typing import Optional
2
+ from typing_extensions import Self
3
3
 
4
4
  from nonebot import get_driver, get_plugin_config
5
- from nonebot.compat import custom_validation
6
5
  from pydantic import BaseModel, Field
7
6
 
7
+ from nonebot_plugin_htmlrender.compat import model_validator
8
8
  from nonebot_plugin_htmlrender.consts import BROWSER_CHANNEL_TYPES, BROWSER_ENGINE_TYPES
9
9
 
10
10
 
11
- @custom_validation
12
- class BrowserEngineType(str):
13
- """Playwright浏览器引擎类型,支持的值由 BROWSER_ENGINE_TYPES 定义。"""
14
-
15
- ALLOWED_VALUES: ClassVar = BROWSER_ENGINE_TYPES
16
-
17
- @classmethod
18
- def __get_validators__(cls) -> Generator[Callable[..., Any], None, None]:
19
- yield cls.validate
20
-
21
- @classmethod
22
- def validate(cls, value: Any) -> str:
23
- if value not in cls.ALLOWED_VALUES:
24
- raise ValueError(
25
- f"Invalid browser type: {value!r}, must be one of {cls.ALLOWED_VALUES}"
26
- )
27
- return value
28
-
29
-
30
- @custom_validation
31
- class BrowserChannelType(str):
32
- """Playwright浏览器通道类型,支持的值由 BROWSER_CHANNEL_TYPES 定义。"""
33
-
34
- ALLOWED_VALUES: ClassVar = BROWSER_CHANNEL_TYPES
35
-
36
- @classmethod
37
- def __get_validators__(cls) -> Generator[Callable[..., Any], None, None]:
38
- yield cls.validate
39
-
40
- @classmethod
41
- def validate(cls, value: Any) -> str:
42
- if value not in cls.ALLOWED_VALUES:
43
- raise ValueError(
44
- f"Invalid channel: {value!r}, must be one of {cls.ALLOWED_VALUES}"
45
- )
46
- return value
47
-
48
-
49
11
  class Config(BaseModel):
50
12
  """插件配置类。"""
51
13
 
52
- htmlrender_browser: BrowserEngineType = Field(
14
+ htmlrender_browser: str = Field(
53
15
  default="chromium",
54
16
  description="Playwright浏览器引擎类型,默认值为 'chromium'。",
55
17
  )
@@ -65,7 +27,7 @@ class Config(BaseModel):
65
27
  htmlrender_proxy_host_bypass: Optional[str] = Field(
66
28
  default=None, description="Playwright浏览器代理的绕过地址。"
67
29
  )
68
- htmlrender_browser_channel: Optional[BrowserChannelType] = Field(
30
+ htmlrender_browser_channel: Optional[str] = Field(
69
31
  default=None, description="Playwright浏览器通道类型。"
70
32
  )
71
33
  htmlrender_browser_executable_path: Optional[str] = Field(
@@ -75,6 +37,25 @@ class Config(BaseModel):
75
37
  default=None, description="通过 CDP 连接Playwright浏览器的端点地址。"
76
38
  )
77
39
 
40
+ @model_validator(mode="after")
41
+ def check_browser_channel(self) -> Self:
42
+ if (
43
+ self.htmlrender_browser_channel is not None
44
+ and self.htmlrender_browser_channel not in BROWSER_CHANNEL_TYPES
45
+ ):
46
+ raise ValueError(
47
+ f"Invalid browser channel type. Must be one of {BROWSER_CHANNEL_TYPES}"
48
+ )
49
+ return self
50
+
51
+ @model_validator(mode="after")
52
+ def check_browser(self) -> Self:
53
+ if self.htmlrender_browser not in BROWSER_ENGINE_TYPES:
54
+ raise ValueError(
55
+ f"Invalid browser type. Must be one of {BROWSER_ENGINE_TYPES}"
56
+ )
57
+ return self
58
+
78
59
 
79
60
  global_config = get_driver().config
80
61
  plugin_config = get_plugin_config(Config)
@@ -19,8 +19,8 @@ WINDOWS = sys.platform.startswith("win") or (sys.platform == "cli" and os.name =
19
19
  # MINGW = sysconfig.get_platform().startswith("mingw")
20
20
  # MACOS = sys.platform == "darwin"
21
21
  MIRRORS = [
22
- MirrorSource("官方", "https://playwright.azureedge.net", 1),
23
- MirrorSource("淘宝", "https://registry.npmmirror.com/-/binary/playwright", 2),
22
+ MirrorSource("Default", "https://playwright.azureedge.net", 1),
23
+ MirrorSource("Taobao", "https://registry.npmmirror.com/-/binary/playwright", 2),
24
24
  ]
25
25
  BROWSER_ENGINE_TYPES = ["chromium", "firefox", "webkit"]
26
26
  BROWSER_CHANNEL_TYPES = [
@@ -77,19 +77,19 @@ async def download_context() -> AsyncIterator[None]:
77
77
  if plugin_config.htmlrender_download_proxy:
78
78
  proxy = plugin_config.htmlrender_download_proxy
79
79
  if proxy.startswith("http://") and not os.environ.get("HTTP_PROXY"):
80
- logger.info(f"已设置 HTTP 代理: {proxy}")
80
+ logger.info(f"Using http Proxy: {proxy}")
81
81
  os.environ["HTTP_PROXY"] = proxy
82
82
  elif proxy.startswith("https://") and not os.environ.get("HTTPS_PROXY"):
83
- logger.info(f"已设置 HTTPS 代理: {proxy}")
83
+ logger.info(f"Using https Proxy: {proxy}")
84
84
  os.environ["HTTPS_PROXY"] = proxy
85
85
 
86
86
  try:
87
87
  best_mirror = await check_mirror_connectivity()
88
88
  if best_mirror is not None:
89
- logger.info(f"使用镜像源: {best_mirror.name} ({best_mirror.url})")
89
+ logger.info(f"Using Mirror source: {best_mirror.name} ({best_mirror.url})")
90
90
  os.environ["PLAYWRIGHT_DOWNLOAD_HOST"] = best_mirror.url
91
91
  else:
92
- logger.info("未找到可用的镜像源,将使用官方源")
92
+ logger.info("Mirror source not available, using default")
93
93
 
94
94
  yield
95
95
 
@@ -143,7 +143,7 @@ async def read_stream(
143
143
  if "|" in text and "%" in text:
144
144
  if text != last_progress:
145
145
  if callback:
146
- await callback(f"下载进度: {text}")
146
+ await callback(f"Progress: {text}")
147
147
  last_progress = text
148
148
  else:
149
149
  if callback:
@@ -153,7 +153,7 @@ async def read_stream(
153
153
  except asyncio.IncompleteReadError:
154
154
  break
155
155
  except Exception as e:
156
- logger.opt(exception=True).error(f"读取流时发生错误: {e!s}")
156
+ logger.opt(exception=True).error(f"Error reading stream: {e!s}")
157
157
  break
158
158
 
159
159
  return "\n".join(output)
@@ -196,18 +196,18 @@ async def execute_install_command(timeout: int) -> tuple[bool, str]:
196
196
  )
197
197
  except asyncio.TimeoutError:
198
198
  await terminate_process(process)
199
- return False, f"安装超时 ({timeout}s)"
199
+ return False, f"Timed out ({timeout}s)"
200
200
 
201
201
  await process.wait()
202
202
  returncode = process.returncode
203
203
 
204
204
  if returncode != 0:
205
- return False, f"安装失败 (exit code: {returncode})"
205
+ return False, f"Exited with code {returncode}"
206
206
 
207
- return True, "安装完成"
207
+ return True, "Installation completed"
208
208
 
209
209
  except Exception as e:
210
- return False, f"安装失败: {e!s}"
210
+ return False, f"An error occurred during installation: {e!s}"
211
211
 
212
212
 
213
213
  async def install_browser(timeout: int = 300) -> bool:
@@ -220,11 +220,20 @@ async def install_browser(timeout: int = 300) -> bool:
220
220
  bool: 是否安装成功。
221
221
  """
222
222
  async with download_context():
223
- logger.info(f"开始安装 <cyan>{plugin_config.htmlrender_browser}</cyan>")
223
+ logger.opt(colors=True).info(
224
+ f"Checking <cyan>{plugin_config.htmlrender_browser}</cyan> installation..."
225
+ )
224
226
  installed, message = await execute_install_command(timeout)
225
227
  if installed:
226
- logger.info("安装成功")
228
+ logger.info("Installation succeeded")
227
229
  return True
228
230
  else:
229
- logger.error(f"安装失败: {message}")
230
- return False
231
+ logger.warning("Installation failed, retrying with official mirror...")
232
+ del os.environ["PLAYWRIGHT_DOWNLOAD_HOST"]
233
+ installed, message = await execute_install_command(timeout)
234
+ if installed:
235
+ logger.info("Installation succeeded")
236
+ return True
237
+ else:
238
+ logger.error(f"Installation failed with: {message}")
239
+ return False
@@ -1,6 +1,6 @@
1
1
  [project]
2
2
  name = "nonebot-plugin-htmlrender"
3
- version = "0.6.0"
3
+ version = "0.6.1"
4
4
  description = "通过浏览器渲染图片"
5
5
  readme = "README.md"
6
6
  authors = [{ name = "kexue", email = "x@kexue-cloud.cn" }]
@@ -1,7 +1,7 @@
1
1
  from collections.abc import AsyncGenerator
2
2
  from typing import cast
3
3
 
4
- from playwright.async_api import Browser, Error, Page
4
+ from playwright.async_api import Browser, Page
5
5
  import pytest
6
6
  from pytest_mock import MockerFixture
7
7
 
@@ -65,14 +65,13 @@ async def test_suppress_and_log(mocker: MockerFixture, exception: Exception) ->
65
65
  async def test_launch(mocker: MockerFixture, browser_config: dict[str, str]) -> None:
66
66
  """测试浏览器启动"""
67
67
  from nonebot_plugin_htmlrender.browser import _launch
68
- from nonebot_plugin_htmlrender.config import BrowserEngineType
69
68
 
70
69
  mock_browser_type = mocker.AsyncMock()
71
70
  mock_playwright = mocker.MagicMock()
72
71
  setattr(mock_playwright, "chromium", mock_browser_type)
73
72
 
74
73
  mocker.patch("nonebot_plugin_htmlrender.browser._playwright", mock_playwright)
75
- await _launch(cast(BrowserEngineType, browser_config["browser"]))
74
+ await _launch(browser_config["browser"])
76
75
 
77
76
  mock_browser_type.launch.assert_called_once()
78
77
 
@@ -94,27 +93,6 @@ async def test_init_browser_success(
94
93
  mock_start.assert_called_once()
95
94
 
96
95
 
97
- @pytest.mark.asyncio
98
- async def test_init_browser_install_fallback(
99
- mocker: MockerFixture, mock_browser: Browser
100
- ) -> None:
101
- """测试浏览器安装回退"""
102
- from nonebot_plugin_htmlrender.browser import init_browser
103
-
104
- mock_start = mocker.patch(
105
- "nonebot_plugin_htmlrender.browser.start_browser",
106
- side_effect=[Error("Executable doesn't exist"), mock_browser],
107
- )
108
- mock_install = mocker.patch(
109
- "nonebot_plugin_htmlrender.browser.install_browser", return_value=True
110
- )
111
-
112
- browser = await init_browser()
113
- assert isinstance(browser, Browser)
114
- assert mock_start.call_count == 2
115
- mock_install.assert_called_once()
116
-
117
-
118
96
  @pytest.mark.asyncio
119
97
  async def test_get_new_page(
120
98
  mocker: MockerFixture,
@@ -273,24 +251,3 @@ async def test_start_browser_with_config(mocker: MockerFixture) -> None:
273
251
 
274
252
  await start_browser()
275
253
  mock_launch.assert_called_with(mocker.ANY, channel="chrome-canary")
276
-
277
-
278
- @pytest.mark.asyncio
279
- async def test_start_browser_failure(mocker: MockerFixture) -> None:
280
- """测试浏览器启动失败"""
281
- from nonebot_plugin_htmlrender.browser import init_browser
282
-
283
- mock_playwright = mocker.AsyncMock()
284
- mock_playwright.start.side_effect = Exception("Failed to start")
285
-
286
- mocker.patch(
287
- "nonebot_plugin_htmlrender.browser.async_playwright",
288
- return_value=mock_playwright,
289
- )
290
- mocker.patch("nonebot_plugin_htmlrender.browser._browser", None)
291
- mocker.patch("nonebot_plugin_htmlrender.browser._playwright", None)
292
-
293
- with pytest.raises(RuntimeError) as exc_info:
294
- await init_browser()
295
-
296
- assert "无法启动浏览器实例" in str(exc_info.value)
@@ -86,7 +86,7 @@ async def test_execute_install_command(mocker: MockerFixture, mock_stream):
86
86
 
87
87
  success, message = await execute_install_command(timeout=5)
88
88
  assert success
89
- assert "安装完成" in message
89
+ assert "Installation completed" in message
90
90
 
91
91
 
92
92
  @pytest.mark.asyncio
@@ -111,7 +111,7 @@ async def test_execute_install_command_timeout(mocker: MockerFixture, mock_strea
111
111
  success, message = await execute_install_command(timeout=1)
112
112
 
113
113
  assert not success
114
- assert message == "安装超时 (1s)"
114
+ assert message == "Timed out (1s)"
115
115
  mock_terminate.assert_called_once_with(mock_process)
116
116
 
117
117