Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
55 commits
Select commit Hold shift + click to select a range
259e54b
Tighten code quality and forward-compat across utils
JE-Chen Jun 26, 2026
75dea79
Avoid shared mutable default args and merge prefix checks
JE-Chen Jun 26, 2026
7754816
Declare test and lint toolchain in dev_requirements
JE-Chen Jun 26, 2026
a5a546c
Modernize type hints to PEP 585/604
JE-Chen Jun 26, 2026
336802d
Cache pip installs in CI and drop redundant pytest install
JE-Chen Jun 26, 2026
c2ea28e
Fix Appium session start for Appium-Python-Client v3+
JE-Chen Jun 26, 2026
edd9b55
Add pytest config so collecting whole test dirs no longer crashes
JE-Chen Jun 26, 2026
8685a6a
Ship PEP 561 py.typed marker and widen Dependabot coverage
JE-Chen Jun 26, 2026
9f9e033
Add SECURITY.md vulnerability disclosure policy
JE-Chen Jun 26, 2026
edd8d27
Fix Postgres testcontainer kwarg for testcontainers v4
JE-Chen Jun 26, 2026
d34771b
Add unit tests for previously untested utilities
JE-Chen Jun 26, 2026
e82382a
Add CLI entry-point smoke test
JE-Chen Jun 26, 2026
186dbdb
Decode subprocess test output as UTF-8
JE-Chen Jun 26, 2026
8bd1813
Write reports and project templates as UTF-8
JE-Chen Jun 26, 2026
7742c61
Fix RST inline-literal warnings in Chinese docs
JE-Chen Jun 26, 2026
f5489d1
Complete PyPI metadata: SPDX license, versions, topics, keywords
JE-Chen Jun 26, 2026
e05c7f0
Add CONTRIBUTING guide
JE-Chen Jun 26, 2026
a71b453
Cover otp_interceptor backends and download_verify branches
JE-Chen Jun 26, 2026
89764b6
Cover backend_log_correlator Loki/ES adapters and parsers
JE-Chen Jun 26, 2026
d55468e
Cover openapi_to_e2e spec-engine edge branches
JE-Chen Jun 26, 2026
d3ddd31
Fix unreachable webdriver-options validation guard
JE-Chen Jun 26, 2026
7494451
Clean up Keys re-export and unused loop variable
JE-Chen Jun 26, 2026
622338d
Fix check_values docstring direction and cover detail checkers
JE-Chen Jun 26, 2026
8669d7a
Harden tar extraction with data filter and cover tar path
JE-Chen Jun 26, 2026
79bb617
Apply key callback in pagination sort check and fix CSP line-number p…
JE-Chen Jun 26, 2026
5d6dcb8
Preserve falsy values when coalescing dual-key report id and httpOnly
JE-Chen Jun 26, 2026
c7fadb7
Remove dead timestamp tracking in session_to_test converter
JE-Chen Jun 26, 2026
bfc38e9
Render live dashboard rows via textContent to prevent DOM XSS
JE-Chen Jun 26, 2026
4976615
Guard empty payment log and compare SSE ids numerically
JE-Chen Jun 26, 2026
036591f
Fail PKCE replay assertion when no results were tested
JE-Chen Jun 26, 2026
f956dd5
Drop unused loop control variables flagged by B007
JE-Chen Jun 26, 2026
f724307
Avoid redundant dict.keys() membership and iteration
JE-Chen Jun 26, 2026
5f7440c
Use lock context manager and logger in HTML report writer
JE-Chen Jun 26, 2026
59817ad
Use lock context manager in JSON and XML report writers
JE-Chen Jun 26, 2026
722edd2
Preserve explicit zero confidence in LLM failure tagger
JE-Chen Jun 26, 2026
02ac37b
Preserve zero volume and pitch in speech utterance parsing
JE-Chen Jun 26, 2026
fadc3bc
Wait on shutdown event instead of busy-looping in driver entrypoint
JE-Chen Jun 26, 2026
44f2d8e
Type on_element ActionChains params as Optional
JE-Chen Jun 26, 2026
96a79c2
Fix chromium webdriver-manager entry not being callable
JE-Chen Jun 26, 2026
73eed24
Skip driver install for managerless browsers and honor cache_manager
JE-Chen Jun 26, 2026
5547821
Make WebDriverWrapper.quit idempotent and reset driver state
JE-Chen Jun 26, 2026
b46a36d
Use ChromeOptions for chromium to set browserName at launch
JE-Chen Jun 26, 2026
5e4876a
Ignore webdriver-manager driver cache directory
JE-Chen Jun 26, 2026
f1f6341
Rebind ActionChains when switching active webdriver
JE-Chen Jun 26, 2026
d9e93f3
Pass chromedriver log via log_output so it is honored on Selenium 4
JE-Chen Jun 26, 2026
d641673
Register appium and cloud drivers via set_active_driver for ActionChains
JE-Chen Jun 26, 2026
877d5fe
Reject non-list action_list instead of swallowing the validation error
JE-Chen Jun 27, 2026
6952a30
Wrap action-JSON write failures in WebRunnerJsonException
JE-Chen Jun 27, 2026
278515b
Fix BiDi network handlers to use Selenium 4 add_request_handler API
JE-Chen Jun 27, 2026
49a8c0b
Fix appium gesture W3C fallback to use execute(actions) not perform_a…
JE-Chen Jun 27, 2026
9feae77
Use lock context manager in JUnit XML report writer
JE-Chen Jun 27, 2026
4fbee5e
Include underlying error in re-raised driver lifecycle exceptions
JE-Chen Jul 9, 2026
525c284
Support Selenium 4.45 native add_response_handler in BiDi network wra…
JE-Chen Jul 9, 2026
c456b51
Suppress confirmed false-positive static-analysis findings
JE-Chen Jul 9, 2026
f702187
Suppress Bandit/Semgrep false positives in test suite
JE-Chen Jul 9, 2026
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
The table of contents is too big for display.
Diff view
Diff view
  •  
  •  
  •  
4 changes: 4 additions & 0 deletions .github/dependabot.yml
Original file line number Diff line number Diff line change
Expand Up @@ -9,3 +9,7 @@ updates:
directory: "/" # Location of package manifests
schedule:
interval: "daily"
- package-ecosystem: "github-actions" # Keep workflow action versions current
directory: "/"
schedule:
interval: "weekly"
2 changes: 2 additions & 0 deletions .github/workflows/e2e_browser.yml
Original file line number Diff line number Diff line change
Expand Up @@ -41,6 +41,8 @@ jobs:
uses: actions/setup-python@v5
with:
python-version: "3.12"
cache: 'pip'
cache-dependency-path: dev_requirements.txt

- name: Install dependencies
run: |
Expand Down
5 changes: 4 additions & 1 deletion .github/workflows/test_dev.yml
Original file line number Diff line number Diff line change
Expand Up @@ -27,12 +27,13 @@ jobs:
uses: actions/setup-python@v5
with:
python-version: ${{ matrix.python-version }}
cache: 'pip'
cache-dependency-path: dev_requirements.txt

- name: Install dependencies
run: |
python -m pip install --upgrade pip wheel
pip install -r dev_requirements.txt
pip install pytest

- name: Run unit tests
run: python -m pytest test/unit_test/test_*.py -v
Expand Down Expand Up @@ -63,6 +64,8 @@ jobs:
uses: actions/setup-python@v5
with:
python-version: ${{ matrix.python-version }}
cache: 'pip'
cache-dependency-path: dev_requirements.txt

- name: Install dependencies
run: |
Expand Down
4 changes: 4 additions & 0 deletions .github/workflows/test_stable.yml
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,8 @@ jobs:
uses: actions/setup-python@v5
with:
python-version: ${{ matrix.python-version }}
cache: 'pip'
cache-dependency-path: requirements.txt

- name: Install dependencies
run: |
Expand Down Expand Up @@ -63,6 +65,8 @@ jobs:
uses: actions/setup-python@v5
with:
python-version: ${{ matrix.python-version }}
cache: 'pip'
cache-dependency-path: requirements.txt

- name: Install dependencies
run: |
Expand Down
12 changes: 12 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -123,9 +123,21 @@ dmypy.json
# Driver
*.zip
*.exe
# webdriver-manager driver cache (set_driver caches drivers under cwd)
.wdm/

/.claude/
/.claude
issues.json
hotspots.json
codacy.json

# Static-analysis exports (SonarCloud / Codacy / pyflakes scratch files)
.codacy_*.json
.sonar_*.json
.pyflakes_files*.txt

# Local report-generation scratch output
default_name*.html
default_name*.json
default_name*.xml
61 changes: 61 additions & 0 deletions CONTRIBUTING.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,61 @@
# Contributing to WebRunner

Thanks for your interest in improving WebRunner (`je_web_runner`). This
guide covers the local setup, the checks we expect to pass, and the branch
/ PR conventions.

## Development setup

WebRunner targets **Python 3.10+**.

```bash
# Runtime dependencies
pip install -r requirements.txt

# Development dependencies (tests, linters, docs, build tooling)
pip install -r dev_requirements.txt
```

## Running the tests

```bash
# Whole suite (unit + integration; e2e tests skip without a Selenium hub)
python -m pytest test/

# Unit tests only
python -m pytest test/unit_test/test_*.py
```

The pure-Python unit suite needs no browser. Integration tests spawn
short-lived subprocesses. The real-browser e2e tests under
`test/e2e_test/` skip cleanly unless `WEBRUNNER_E2E_HUB` points at a
reachable Selenium Grid (see `docker/`).

## Before opening a pull request

Run the linters and keep them clean (no new findings):

```bash
python -m flake8 je_web_runner/ --max-line-length=120 --max-complexity=10
python -m pylint je_web_runner/
python -m bandit -r je_web_runner/ -ll
python -m mypy je_web_runner/
```

Please also:

- Add or update tests for any behaviour you change.
- Keep public functions type-hinted and documented.
- Avoid introducing new static-analysis issues (SonarQube / Codacy).

## Branches and commits

- `main` is the stable branch; `dev` is the development branch.
- Open pull requests **from `dev` into `main`**.
- Write concise, imperative commit messages (e.g. "Add element
validation", "Fix driver cleanup on timeout").

## Reporting security issues

Do **not** file security vulnerabilities as public issues — see
[SECURITY.md](SECURITY.md) for the private disclosure process.
42 changes: 42 additions & 0 deletions SECURITY.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,42 @@
# Security Policy

## Supported Versions

WebRunner (`je_web_runner`) is distributed on PyPI. Security fixes are
applied to the latest released version. Please upgrade to the most recent
release before reporting an issue, as the problem may already be resolved.

| Version | Supported |
| -------------- | ------------------ |
| Latest release | :white_check_mark: |
| Older releases | :x: |

## Reporting a Vulnerability

Please report security vulnerabilities **privately** — do not open a public
issue, pull request, or discussion for a suspected vulnerability.

Use either of the following private channels:

- **GitHub Security Advisories** — open a private report via the
repository's *Security* tab → *Report a vulnerability*. This is the
preferred channel.
- **Email** — `jechenmailman@gmail.com` with a subject line beginning
`[SECURITY]`.

To help triage quickly, please include where practical:

- A description of the vulnerability and its impact.
- The affected version(s) and platform.
- Step-by-step reproduction instructions or a minimal proof of concept.
- Any suggested remediation.

## Disclosure Process

- We aim to acknowledge a report within **5 business days**.
- We target a fix and coordinated disclosure within **90 days** of the
initial report, sooner for actively exploited issues.
- We will credit reporters who wish to be acknowledged once a fix is
released. Please let us know your preference.

Thank you for helping keep WebRunner and its users safe.
7 changes: 6 additions & 1 deletion dev_requirements.txt
Original file line number Diff line number Diff line change
Expand Up @@ -7,4 +7,9 @@ Pyside6
defusedxml
Pillow
faker
sqlalchemy
sqlalchemy
pytest
pylint
flake8
bandit
mypy
Original file line number Diff line number Diff line change
Expand Up @@ -514,7 +514,7 @@ CODEOWNERS 解析器(GitHub 語意:最後一條 match 的規則勝出)+ 每
* ``web_push_assert`` —— Push subscription VAPID key 匹配、endpoint
白名單、``userVisibleOnly``、``showNotification`` payload。
* ``background_sync_assert`` —— Background Sync register / fire /
retry / ``lastChance``(quota 耗盡)斷言。
retry / ``lastChance``\ (quota 耗盡)斷言。
* ``wake_lock_assert`` —— Screen wake lock acquire / release /
漏掉 / 切回前景時 re-acquire 偵測。
* ``pip_assert`` —— Picture-in-Picture(影片 + Document PiP)
Expand Down Expand Up @@ -569,7 +569,7 @@ Email 與通知送達
================

* ``email_deliverability`` —— SPF / DKIM / DMARC header +
``List-Unsubscribe``(Gmail/Yahoo 大量寄件規則)+ BCC 外洩稽核。
``List-Unsubscribe``\ (Gmail/Yahoo 大量寄件規則)+ BCC 外洩稽核。
* ``inbox_render_outlook`` —— Outlook(Word 引擎)/ Gmail / Apple
Mail 渲染相容性 pre-flight 檢查。
* ``push_delivery`` —— FCM / APNs payload 大小 + 必填欄位 + PII
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -304,7 +304,7 @@ CDP Fetch 攔截
--------------

``Fetch.*`` CDP 命令薄包裝。要實際收事件 (``Fetch.requestPaused``) 請使用
``CDPEventListener``(或 Selenium trio-based devtools listener)自行訂閱:
``CDPEventListener``\ (或 Selenium trio-based devtools listener)自行訂閱:

.. code-block:: python

Expand Down
2 changes: 1 addition & 1 deletion je_web_runner/__main__.py
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,6 @@
if __name__ == "__main__":
try:
sys.exit(main())
except Exception as error: # noqa: BLE001 — surface any failure as exit-1 to the shell
except Exception as error:
print(repr(error), file=sys.stderr)
sys.exit(1)
46 changes: 23 additions & 23 deletions je_web_runner/action_lsp/server.py
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,7 @@
import json
import sys
from dataclasses import dataclass, field
from typing import Any, Dict, List, Optional, TextIO
from typing import Any, TextIO

from je_web_runner.utils.exception.exceptions import WebRunnerException
from je_web_runner.utils.logging.loggin_instance import web_runner_logger
Expand All @@ -36,11 +36,11 @@

@dataclass
class ActionLspServer:
documents: Dict[str, _Document] = field(default_factory=dict)
documents: dict[str, _Document] = field(default_factory=dict)
initialized: bool = False
_command_names: Optional[List[str]] = field(default=None, init=False, repr=False)
_command_names: list[str] | None = field(default=None, init=False, repr=False)

def command_names(self) -> List[str]:
def command_names(self) -> list[str]:
if self._command_names is None:
try:
from je_web_runner.utils.executor.action_executor import executor
Expand All @@ -53,7 +53,7 @@

# --- Top-level dispatch ----------------------------------------------

def handle(self, message: Dict[str, Any]) -> Optional[Dict[str, Any]]:
def handle(self, message: dict[str, Any]) -> dict[str, Any] | None:
method = message.get("method")
request_id = message.get("id")
params = message.get("params") or {}
Expand All @@ -78,7 +78,7 @@

# --- Handlers --------------------------------------------------------

def _initialize(self) -> Dict[str, Any]:
def _initialize(self) -> dict[str, Any]:
return {
"capabilities": {
"textDocumentSync": 1, # full sync
Expand All @@ -87,14 +87,14 @@
"serverInfo": {"name": "webrunner-action-lsp", "version": "0.1.0"},
}

def _on_did_open(self, params: Dict[str, Any]) -> Dict[str, Any]:
def _on_did_open(self, params: dict[str, Any]) -> dict[str, Any]:
document = params.get("textDocument") or {}
uri = str(document.get("uri", ""))
text = str(document.get("text", ""))
self.documents[uri] = _Document(uri=uri, text=text, version=int(document.get("version", 0)))
return self._diagnostics_notification(uri, text)

def _on_did_change(self, params: Dict[str, Any]) -> Dict[str, Any]:
def _on_did_change(self, params: dict[str, Any]) -> dict[str, Any]:
document = params.get("textDocument") or {}
uri = str(document.get("uri", ""))
changes = params.get("contentChanges") or []
Expand All @@ -108,12 +108,12 @@
self.documents[uri].version = int(document.get("version", 0))
return self._diagnostics_notification(uri, full_text)

def _on_did_close(self, params: Dict[str, Any]) -> None:
def _on_did_close(self, params: dict[str, Any]) -> None:
uri = str((params.get("textDocument") or {}).get("uri", ""))
self.documents.pop(uri, None)
return None
return

Check warning on line 114 in je_web_runner/action_lsp/server.py

View check run for this annotation

SonarQubeCloud / SonarCloud Code Analysis

Remove this redundant return.

See more on https://sonarcloud.io/project/issues?id=Integration-Automation_WebRunner&issues=AZ9Hp8l652clM__njHGk&open=AZ9Hp8l652clM__njHGk&pullRequest=100

def _completion(self, _params: Dict[str, Any]) -> Dict[str, Any]:
def _completion(self, _params: dict[str, Any]) -> dict[str, Any]:
# ``_params`` is part of the LSP request shape but the suggestion
# list is identical for every cursor position, so the textDocument
# / position payload is intentionally ignored.
Expand All @@ -130,7 +130,7 @@

# --- Diagnostics -----------------------------------------------------

def _diagnostics_notification(self, uri: str, text: str) -> Dict[str, Any]:
def _diagnostics_notification(self, uri: str, text: str) -> dict[str, Any]:
return {
"jsonrpc": "2.0",
"method": "textDocument/publishDiagnostics",
Expand All @@ -140,7 +140,7 @@
},
}

def _lint_diagnostics(self, text: str) -> List[Dict[str, Any]]:
def _lint_diagnostics(self, text: str) -> list[dict[str, Any]]:
if not text.strip():
return []
try:
Expand All @@ -151,7 +151,7 @@
if not isinstance(actions, list):
return [_diagnostic("Action document root must be a JSON array.",
line=0, severity=1)]
diagnostics: List[Dict[str, Any]] = []
diagnostics: list[dict[str, Any]] = []
try:
from je_web_runner.utils.linter.action_linter import lint_action
except Exception: # pylint: disable=broad-except
Expand All @@ -168,18 +168,18 @@
# --- Helpers ---------------------------------------------------------

@staticmethod
def _respond(request_id: Any, result: Any) -> Dict[str, Any]:
def _respond(request_id: Any, result: Any) -> dict[str, Any]:
return {"jsonrpc": "2.0", "id": request_id, "result": result}

@staticmethod
def _error(request_id: Any, code: int, message: str) -> Dict[str, Any]:
def _error(request_id: Any, code: int, message: str) -> dict[str, Any]:
return {
"jsonrpc": "2.0", "id": request_id,
"error": {"code": code, "message": message},
}


def _diagnostic(error_message: str, line: int, severity: int) -> Dict[str, Any]:
def _diagnostic(error_message: str, line: int, severity: int) -> dict[str, Any]:
return {
"range": {
"start": {"line": max(0, line), "character": 0},
Expand All @@ -196,8 +196,8 @@
_HEADER_TERMINATOR = "\r\n\r\n"


def _read_message(stdin: TextIO) -> Optional[Dict[str, Any]]:
headers: Dict[str, str] = {}
def _read_message(stdin: TextIO) -> dict[str, Any] | None:
headers: dict[str, str] = {}
while True:
line = stdin.readline()
if line == "":
Expand All @@ -224,16 +224,16 @@
raise ActionLspError(f"body is not JSON: {error}") from error


def _write_message(stdout: TextIO, message: Dict[str, Any]) -> None:
def _write_message(stdout: TextIO, message: dict[str, Any]) -> None:
body = json.dumps(message, ensure_ascii=False)
stdout.write(f"Content-Length: {len(body.encode('utf-8'))}\r\n\r\n{body}")
stdout.flush()


def serve_stdio(
stdin: Optional[TextIO] = None,
stdout: Optional[TextIO] = None,
server: Optional[ActionLspServer] = None,
stdin: TextIO | None = None,
stdout: TextIO | None = None,
server: ActionLspServer | None = None,
) -> None:
"""Run the LSP loop until stdin EOF or an ``exit`` notification."""
in_stream = stdin or sys.stdin
Expand Down
Loading
Loading