Skip to content

Commit d59bc00

Browse files
committed
fix: cross-platform path handling and config group isolation
1 parent ec6bdf0 commit d59bc00

5 files changed

Lines changed: 79 additions & 41 deletions

File tree

README.md

Lines changed: 8 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -105,17 +105,21 @@ uv run webqa-agent init -m gen
105105
# Edit config.yaml: target.url, llm_config.api_key
106106
# Configure test_config
107107
# For more details, see "Usage > Generate Mode - Configuration" below
108-
uv run webqa-agent gen # Run Generate Mode
109-
uv run webqa-agent gen -c /path/to/config.yaml -w 4 # Generate Mode with specified config and 4 parallel workers
108+
# Run Generate Mode
109+
uv run webqa-agent gen
110+
# Generate Mode with specified config and 4 parallel workers
111+
uv run webqa-agent gen -c /path/to/config.yaml -w 4
110112

111113
# 4) Run Mode
112114
# Initialize Run mode configuration (config_run.yaml)
113115
uv run webqa-agent init -m run
114116
# Edit config.yaml: target.url, llm_config.api_key
115117
# Write natural language test cases
116118
# For more details, see "Usage > Run Mode - Configuration" below
117-
uv run webqa-agent run # Run Run Mode
118-
uv run webqa-agent run -c /path/to/config_run.yaml -w 4 # Run Mode with specified config and 4 parallel workers
119+
# Run Run Mode
120+
uv run webqa-agent run
121+
# Run Mode with specified config and 4 parallel workers
122+
uv run webqa-agent run -c /path/to/config_run.yaml -w 4
119123
```
120124

121125
### 🔧 Generate Mode - Optional Dependencies
@@ -166,7 +170,6 @@ For more details, please refer to [docs/MODES&CLI.md](docs/MODES&CLI.md)
166170
target:
167171
url: https://example.com # Website URL to test
168172
description: Website QA testing
169-
max_concurrent_tests: 2 # Optional, default 2
170173

171174
test_config:
172175
function_test: # Functional testing
@@ -185,20 +188,13 @@ llm_config: # LLM configuration, supports OpenAI, An
185188
filter_model: gpt-4o-mini # Lightweight model for element filtering (optional)
186189
api_key: your_api_key # Or set via environment variable (OPENAI_API_KEY)
187190
base_url: https://api.openai.com/v1 # Optional, API endpoint. For OpenAI-compatible models (Doubao, Qwen, etc.), set to their API endpoint
188-
temperature: 0.1 # Optional, model temperature
189-
# For detailed configuration examples (OpenAI, Claude, Gemini) and reasoning settings,
190-
# see config/config.yaml.example
191191

192192
browser_config:
193-
viewport: {"width": 1280, "height": 720}
194193
headless: False # Auto True in Docker
195194
language: en-US
196195

197196
report:
198197
language: en-US # zh-CN or en-US
199-
200-
log:
201-
level: info # debug, info, warning, error
202198
```
203199
204200
### Run Mode - Configuration
@@ -214,7 +210,6 @@ For more details and test case writing specifications, please refer to [docs/MOD
214210
```yaml
215211
target:
216212
url: https://example.com # Target website URL
217-
max_concurrent_tests: 2 # Maximum concurrent test count
218213
219214
llm_config: # LLM configuration
220215
api: openai

README_zh-CN.md

Lines changed: 8 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -105,17 +105,21 @@ uv run webqa-agent init -m gen
105105
# 编辑 config.yaml:target.url、llm_config.api_key
106106
# 配置 test_config
107107
# 更多说明见下方“使用说明 > Gen 模式 - 配置介绍”
108-
uv run webqa-agent gen # 运行 Gen 模式
109-
uv run webqa-agent gen -c /path/to/config.yaml -w 4 # 指定配置文件路径,使用 4 个并行 Worker
108+
# 运行 Gen 模式
109+
uv run webqa-agent gen
110+
# 指定配置文件路径,使用 4 个并行 Worker
111+
uv run webqa-agent gen -c /path/to/config.yaml -w 4
110112

111113
# 4) Run 模式
112114
# 初始化 Run 模式配置 (config_run.yaml)
113115
uv run webqa-agent init -m run
114116
# 编辑 config.yaml:target.url、llm_config.api_key
115117
# 编写自然语言用例
116118
# 更多说明见下方“使用说明 > Run 模式 - 配置介绍”
117-
uv run webqa-agent run # 运行 Run 模式
118-
uv run webqa-agent run -c /path/to/config.yaml -w 4 # 指定配置文件路径,使用 4 个并行 Worker
119+
# 运行 Run 模式
120+
uv run webqa-agent run
121+
# 指定配置文件路径,使用 4 个并行 Worker
122+
uv run webqa-agent run -c /path/to/config.yaml -w 4
119123
```
120124

121125
### 🔧 Generate 模式 - 可选依赖
@@ -166,7 +170,6 @@ curl -fsSL https://raw.githubusercontent.com/MigoXLab/webqa-agent/main/start.sh
166170
target:
167171
url: https://example.com # 需要测试的网站 URL
168172
description: 网站质量保证测试
169-
max_concurrent_tests: 2 # 可选,默认并发数 2
170173

171174
test_config:
172175
function_test: # 功能测试
@@ -185,22 +188,13 @@ llm_config: # LLM 配置,支持 OpenAI、Anthropic
185188
filter_model: gpt-4o-mini # 轻量级模型用于元素过滤(可选)
186189
api_key: your_api_key # 或通过环境变量设置 (OPENAI_API_KEY)
187190
base_url: https://api.openai.com/v1 # 可选,API 端点。对于 OpenAI 兼容格式模型(豆包、通义千问等),设置为对应的 API 端点
188-
temperature: 0.1 # 可选,
189-
# 详细的配置示例(OpenAI、Claude、Gemini)和推理设置说明,
190-
# 请参考 config/config.yaml.example
191191

192192
browser_config:
193-
viewport: {"width": 1280, "height": 720}
194193
headless: False # Docker 环境自动设为 True
195194
language: en-US
196-
cookies: []
197-
save_screenshots: False
198195

199196
report:
200197
language: en-US # zh-CN 或 en-US
201-
202-
log:
203-
level: info # debug, info, warning, error
204198
```
205199
206200
### Run 模式 - 配置介绍
@@ -216,7 +210,6 @@ Run 模式配置文件需包含 `cases` 字段,用于定义具体的测试用
216210
```yaml
217211
target:
218212
url: https://example.com # 目标网站 URL
219-
max_concurrent_tests: 2 # 最大并发测试数
220213
221214
llm_config: # LLM 配置
222215
api: openai

webqa_agent/actions/action_handler.py

Lines changed: 3 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,7 @@
88
import re
99
from contextvars import ContextVar
1010
from dataclasses import dataclass, field
11-
from pathlib import Path
11+
from pathlib import Path, PurePosixPath
1212
from typing import Any, Dict, List, Optional, Union
1313

1414
from playwright.async_api import Frame, Page
@@ -1733,9 +1733,8 @@ async def b64_page_screenshot(
17331733
file_path = session_dir / filename
17341734
file_path_str = str(file_path)
17351735

1736-
# Return path relative to the report root for HTML rendering
1737-
# Screenshots are stored in report_dir/screenshots/ and report is in report_dir/run_report.html
1738-
relative_path = os.path.join(session_dir.name, filename)
1736+
# Use POSIX separators to keep HTML assets valid on Windows/macOS/Linux.
1737+
relative_path = str(PurePosixPath(session_dir.name) / filename)
17391738

17401739
# Capture screenshot (always returns bytes)
17411740
screenshot_bytes = await self.take_screenshot(

webqa_agent/executor/case_mode.py

Lines changed: 26 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -242,15 +242,33 @@ async def run(
242242
logging.error(f"{icon['cross']} {friendly_msg}")
243243
raise ValueError(friendly_msg) from e
244244

245-
# Initialize case executor with explicit report_dir
246-
case_executor = CaseExecutor(
247-
test_config=test_config,
248-
llm_config=llm_config,
249-
report_dir=report_dir
250-
)
245+
case_results = []
246+
case_executor = None
251247

252-
# Execute all cases
253-
case_results = await case_executor.execute_cases(cases=cases, workers=workers)
248+
# Split cases by originating config in order to isolate browser sessions between groups
249+
case_groups = []
250+
start_idx = 0
251+
for config in configs:
252+
cfg_cases = config.get('cases', [])
253+
if not cfg_cases:
254+
continue
255+
end_idx = start_idx + len(cfg_cases)
256+
case_groups.append(cases[start_idx:end_idx])
257+
start_idx = end_idx
258+
259+
for group_index, group_cases in enumerate(case_groups, start=1):
260+
logging.info(f'Executing config #{group_index} ({len(group_cases)} case(s)) with isolated sessions')
261+
case_executor = CaseExecutor(
262+
test_config=test_config,
263+
llm_config=llm_config,
264+
report_dir=report_dir,
265+
)
266+
267+
group_results = await case_executor.execute_cases(cases=group_cases, workers=workers)
268+
if group_results:
269+
case_results.extend(group_results)
270+
else:
271+
logging.warning(f'No results returned for config #{group_index}')
254272

255273
if case_results is None:
256274
logging.warning('case_executor.execute_cases returned None, treating as empty list')

webqa_agent/executor/result_aggregator.py

Lines changed: 34 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -203,7 +203,13 @@ def _add_files_from_dir(directory: Path):
203203
index_data['count'] = count
204204
aggregated[mode]['index'] = index_data
205205
except Exception as backfill_err:
206-
logging.warning(f'Failed to backfill aggregated results: {backfill_err}')
206+
logging.warning(f'Failed to backfill aggregated results: {backfill_err}')
207+
208+
# Normalize any file-system paths to POSIX style so HTML works cross-platform
209+
try:
210+
aggregated = self._normalize_paths_for_web(aggregated)
211+
except Exception as norm_err:
212+
logging.warning(f'Failed to normalize paths for web: {norm_err}')
207213

208214
# Write the aggregated results to test_results.json
209215
output_path = os.path.join(report_dir, 'test_results.json')
@@ -293,6 +299,29 @@ def _serialize_data_for_inline(data: Any) -> str:
293299
)
294300
return safe
295301

302+
@staticmethod
303+
def _normalize_paths_for_web(data: Any) -> Any:
304+
"""Convert screenshot paths to web-friendly separators.
305+
306+
Windows paths use backslashes, which break relative asset loading in
307+
the generated HTML. This recursively walks the data structure and
308+
rewrites strings that look like screenshot paths to POSIX style.
309+
"""
310+
311+
def _normalize(value: Any) -> Any:
312+
if isinstance(value, dict):
313+
normalized = {k: _normalize(v) for k, v in value.items()}
314+
if normalized.get('type') == 'path' and isinstance(normalized.get('data'), str):
315+
normalized['data'] = normalized['data'].replace('\\', '/')
316+
return normalized
317+
if isinstance(value, list):
318+
return [_normalize(item) for item in value]
319+
if isinstance(value, str) and 'screenshots' in value:
320+
return value.replace('\\', '/')
321+
return value
322+
323+
return _normalize(data)
324+
296325
def generate_html_report_fully_inlined(self, test_session, report_dir: str | None = None, aggregated_data: Dict[str, Any] = None) -> str:
297326
"""Generate a fully inlined HTML report for the test session."""
298327
import json
@@ -329,6 +358,10 @@ def generate_html_report_fully_inlined(self, test_session, report_dir: str | Non
329358

330359
if data is None:
331360
data = test_session.to_dict()
361+
362+
# Ensure any Windows-style paths are safe for browser consumption
363+
data = self._normalize_paths_for_web(data)
364+
332365
safe_data_json = self._serialize_data_for_inline(data)
333366
datajs_content = f'window.testResultData = {safe_data_json};'
334367

0 commit comments

Comments
 (0)