soleprint init commit
This commit is contained in:
153
station/tools/tester/playwright/discovery.py
Normal file
153
station/tools/tester/playwright/discovery.py
Normal file
@@ -0,0 +1,153 @@
|
||||
"""
|
||||
Discover Playwright tests (.spec.ts files).
|
||||
"""
|
||||
|
||||
import re
|
||||
from pathlib import Path
|
||||
from typing import Optional
|
||||
from dataclasses import dataclass
|
||||
|
||||
|
||||
@dataclass
|
||||
class PlaywrightTestInfo:
|
||||
"""Information about a discovered Playwright test."""
|
||||
id: str
|
||||
name: str
|
||||
file_path: str
|
||||
test_name: str
|
||||
description: Optional[str] = None
|
||||
gherkin_feature: Optional[str] = None
|
||||
gherkin_scenario: Optional[str] = None
|
||||
tags: list[str] = None
|
||||
|
||||
def __post_init__(self):
|
||||
if self.tags is None:
|
||||
self.tags = []
|
||||
|
||||
|
||||
def discover_playwright_tests(tests_dir: Path) -> list[PlaywrightTestInfo]:
|
||||
"""
|
||||
Discover all Playwright tests in the frontend-tests directory.
|
||||
|
||||
Parses .spec.ts files to extract:
|
||||
- test() calls
|
||||
- describe() blocks
|
||||
- Gherkin metadata from comments
|
||||
- Tags from comments
|
||||
"""
|
||||
if not tests_dir.exists():
|
||||
return []
|
||||
|
||||
tests = []
|
||||
|
||||
# Find all .spec.ts files
|
||||
for spec_file in tests_dir.rglob("*.spec.ts"):
|
||||
relative_path = spec_file.relative_to(tests_dir)
|
||||
|
||||
# Read file content
|
||||
try:
|
||||
content = spec_file.read_text()
|
||||
except Exception:
|
||||
continue
|
||||
|
||||
# Extract describe blocks and tests
|
||||
tests_in_file = _parse_playwright_file(content, spec_file, relative_path)
|
||||
tests.extend(tests_in_file)
|
||||
|
||||
return tests
|
||||
|
||||
|
||||
def _parse_playwright_file(
|
||||
content: str,
|
||||
file_path: Path,
|
||||
relative_path: Path
|
||||
) -> list[PlaywrightTestInfo]:
|
||||
"""Parse a Playwright test file to extract test information."""
|
||||
tests = []
|
||||
|
||||
# Pattern to match test() calls
|
||||
# test('test name', async ({ page }) => { ... })
|
||||
# test.only('test name', ...)
|
||||
test_pattern = re.compile(
|
||||
r"test(?:\.\w+)?\s*\(\s*['\"]([^'\"]+)['\"]",
|
||||
re.MULTILINE
|
||||
)
|
||||
|
||||
# Pattern to match describe() blocks
|
||||
describe_pattern = re.compile(
|
||||
r"describe\s*\(\s*['\"]([^'\"]+)['\"]",
|
||||
re.MULTILINE
|
||||
)
|
||||
|
||||
# Extract metadata from comments above tests
|
||||
# Looking for JSDoc-style comments with metadata
|
||||
metadata_pattern = re.compile(
|
||||
r"/\*\*\s*\n((?:\s*\*.*\n)+)\s*\*/\s*\n\s*test",
|
||||
re.MULTILINE
|
||||
)
|
||||
|
||||
# Find all describe blocks to use as context
|
||||
describes = describe_pattern.findall(content)
|
||||
describe_context = describes[0] if describes else None
|
||||
|
||||
# Find all tests
|
||||
for match in test_pattern.finditer(content):
|
||||
test_name = match.group(1)
|
||||
|
||||
# Look for metadata comment before this test
|
||||
# Search backwards from the match position
|
||||
before_test = content[:match.start()]
|
||||
metadata_match = None
|
||||
for m in metadata_pattern.finditer(before_test):
|
||||
metadata_match = m
|
||||
|
||||
# Parse metadata if found
|
||||
gherkin_feature = None
|
||||
gherkin_scenario = None
|
||||
tags = []
|
||||
description = None
|
||||
|
||||
if metadata_match:
|
||||
metadata_block = metadata_match.group(1)
|
||||
|
||||
# Extract Feature, Scenario, Tags from metadata
|
||||
feature_match = re.search(r"\*\s*Feature:\s*(.+)", metadata_block)
|
||||
scenario_match = re.search(r"\*\s*Scenario:\s*(.+)", metadata_block)
|
||||
tags_match = re.search(r"\*\s*Tags:\s*(.+)", metadata_block)
|
||||
desc_match = re.search(r"\*\s*@description\s+(.+)", metadata_block)
|
||||
|
||||
if feature_match:
|
||||
gherkin_feature = feature_match.group(1).strip()
|
||||
if scenario_match:
|
||||
gherkin_scenario = scenario_match.group(1).strip()
|
||||
if tags_match:
|
||||
tags_str = tags_match.group(1).strip()
|
||||
tags = [t.strip() for t in re.findall(r"@[\w-]+", tags_str)]
|
||||
if desc_match:
|
||||
description = desc_match.group(1).strip()
|
||||
|
||||
# Build test ID
|
||||
module_name = str(relative_path).replace("/", ".").replace(".spec.ts", "")
|
||||
test_id = f"frontend.{module_name}.{_sanitize_test_name(test_name)}"
|
||||
|
||||
tests.append(PlaywrightTestInfo(
|
||||
id=test_id,
|
||||
name=test_name,
|
||||
file_path=str(relative_path),
|
||||
test_name=test_name,
|
||||
description=description or test_name,
|
||||
gherkin_feature=gherkin_feature,
|
||||
gherkin_scenario=gherkin_scenario,
|
||||
tags=tags,
|
||||
))
|
||||
|
||||
return tests
|
||||
|
||||
|
||||
def _sanitize_test_name(name: str) -> str:
|
||||
"""Convert test name to a valid identifier."""
|
||||
# Replace spaces and special chars with underscores
|
||||
sanitized = re.sub(r"[^\w]+", "_", name.lower())
|
||||
# Remove leading/trailing underscores
|
||||
sanitized = sanitized.strip("_")
|
||||
return sanitized
|
||||
Reference in New Issue
Block a user