Skip to content

Commit 37450ee

Browse files
authored
Merge pull request #491 from webdriverio/fix/python-pytest-demo-live
Let an explicit devtools_trace=false win; run the pytest demo live by default
2 parents 058f111 + b402609 commit 37450ee

10 files changed

Lines changed: 419 additions & 164 deletions

File tree

‎.github/workflows/ci.yml‎

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -163,8 +163,9 @@ jobs:
163163
- name: 🧪 Nightwatch BDD (examples/nightwatch/bdd)
164164
if: ${{ !cancelled() && steps.fixture.outcome == 'success' }}
165165
run: bash .github/scripts/examples-smoke/run-example.sh nightwatch-bdd pnpm demo:nightwatch
166-
# The committed pytest.ini keeps only failing tests' archives; one archive
167-
# for the whole run is what the expectations describe.
166+
# This job's DEVTOOLS_MODE=trace reaches the example through its conftest.py,
167+
# whose defaults keep only failing tests' archives; one archive for the whole
168+
# run is what the expectations describe, and `-o` outranks those defaults.
168169
- name: 🧪 Python + pytest (examples/selenium-py/pytest)
169170
if: ${{ !cancelled() && steps.fixture.outcome == 'success' }}
170171
run: >-

‎CLAUDE.md‎

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -26,7 +26,7 @@ Run from repo root unless noted.
2626
| `pnpm test:watch` | Run vitest in watch mode. |
2727
| `pnpm test:coverage` | Run vitest with v8 coverage. The thresholds in `vitest.config.ts` are aspirational, not a gate: that file states CI does not run this and the suite is currently below all four. CI runs `test`/`lint`/`test:ui`. |
2828
| `pnpm lint` | Lint all packages in parallel. Includes `eslint-plugin-security` for a subset of CodeQL findings; deeper taint-flow checks surface on the PR's CodeQL scan. |
29-
| `pnpm demo:wdio` / `pnpm demo:nightwatch` / `pnpm demo:selenium` | Run the per-framework example projects. Useful for manual verification of UI or runtime changes. **No package pins a browser driver**, deliberately: a pinned `chromedriver` rots against whatever Chrome the developer has, and because pnpm puts the package's `node_modules/.bin` on `PATH`, Selenium Manager finds it there, prefers it over resolving, and on a major mismatch only *warns* before returning it anyway. The session then fails to start. With nothing on `PATH` both adapters resolve a matching driver (Nightwatch's Chrome service builder sets `requiresDriverBinary: false` and passes an unset `server_path` through to the same resolver), and nightwatch declares `chromedriver` an optional peer. |
29+
| `pnpm demo:wdio` / `pnpm demo:nightwatch` / `pnpm demo:selenium` / `pnpm demo:python:pytest` | Run the per-framework example projects, live by default; `DEVTOOLS_MODE=trace` flips them to trace mode. The Python adapter does not read that variable, so the pytest example's `conftest.py` maps it onto the plugin's options (per-test archives, `retain-on-failure`), yielding to anything set on the command line, in its `pytest.ini` or in the environment. Useful for manual verification of UI or runtime changes. **No package pins a browser driver**, deliberately: a pinned `chromedriver` rots against whatever Chrome the developer has, and because pnpm puts the package's `node_modules/.bin` on `PATH`, Selenium Manager finds it there, prefers it over resolving, and on a major mismatch only *warns* before returning it anyway. The session then fails to start. With nothing on `PATH` both adapters resolve a matching driver (Nightwatch's Chrome service builder sets `requiresDriverBinary: false` and passes an unset `server_path` through to the same resolver), and nightwatch declares `chromedriver` an optional peer. |
3030
| `pnpm demo:wdio:mobile` / `:selenium:mobile` / `:nightwatch:mobile` / `:python:mobile` | The same, against Appium. All four build the same capability bag and drive the Clock app that ships with every Android system image (keying a duration on the timer keypad, never starting a timer), so a native example needs no `.apk`. `DEVTOOLS_MOBILE_PLATFORM=ios` runs the iOS spec instead — all four adapters — which drives Settings because the simulator ships no Clock, from a separate spec per platform rather than a branch. The simulator is chosen by udid and defaults to whichever is already booted, and an unmatched `IOS_DEVICE_NAME` is refused: naming one that does not exist makes the XCUITest driver create and boot it, every run, rather than fail. That refusal and the booted-simulator preflight are **local** policy — `xcrun simctl` enumerates local simulators and nothing else — so `IOS_UDID` and a non-local `APPIUM_HOST` both bypass them, or a real device and a cloud grid would be refused a run they were correctly configured for. `DEVTOOLS_MOBILE=web` drives the device's own browser on both platforms — Chrome on Android, Safari on iOS, which the XCUITest driver serves without a chromedriver. `examples/MOBILE.md` holds the prerequisites and the `DEVTOOLS_MOBILE` / `APPIUM_APP` switches; `DEVTOOLS_MODE=trace` flips any demo to trace mode. |
3131
| `pnpm dev` | Run all packages in parallel dev mode. |
3232
| `pnpm smoke:fixtures [--port N]` | Serve the local fixture site the desktop examples drive (a copy of the-internet's login flow: `/`, `/login`, `/secure`, `/logout`; default `http://127.0.0.1:4567`). Every desktop login example reads `DEVTOOLS_EXAMPLE_BASE_URL` (trailing slash ignored); unset, they drive the public site. |

‎examples/README.md‎

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -32,7 +32,7 @@ one is comparable with a trace from another.
3232
| `pnpm demo:wdio:retention` | WebdriverIO + Cucumber, a passing and a failing spec under a retention policy |
3333
| `pnpm demo:python` | Python, plain script |
3434
| `pnpm demo:python:login` | Python, plain script (login flow) |
35-
| `pnpm demo:python:pytest` | Python + pytest |
35+
| `pnpm demo:python:pytest` | Python + pytest; under `DEVTOOLS_MODE=trace`, one archive per failing test |
3636
| `pnpm demo:wdio:mobile` | WebdriverIO against Appium |
3737
| `pnpm demo:nightwatch:mobile` | Nightwatch against Appium |
3838
| `pnpm demo:selenium:mobile` | Selenium against Appium |
@@ -43,6 +43,8 @@ runner directly — `example:mocha`, `example:jest`, `example:cucumber`, or
4343
`example:mocha:allure` for the Allure variant.
4444

4545
`DEVTOOLS_MODE=trace` flips a demo to trace mode; the default is live.
46+
The Python adapter does not read it, so the pytest example's `conftest.py`
47+
translates it into the plugin's own options.
4648
`DEVTOOLS_TRACE_GRANULARITY` and `DEVTOOLS_TRACE_POLICY` walk the rest of the
4749
ladder, so each runner needs only one config.
4850

Lines changed: 36 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,36 @@
1+
"""``DEVTOOLS_MODE=trace`` switches this example to trace mode, as it does every JS demo.
2+
3+
The adapter itself does not read ``DEVTOOLS_MODE``; this hook turns it into the
4+
plugin's own options before the plugin resolves them. Each default yields to
5+
anything already chosen on the command line, in ``pytest.ini`` or through
6+
``DEVTOOLS_TRACE_POLICY`` / ``DEVTOOLS_TRACE_GRANULARITY``, so CI's
7+
``-o devtools_trace_policy=on`` still wins.
8+
"""
9+
10+
import os
11+
12+
import pytest
13+
14+
#: (CLI option dest, ini name, environment variable, value this example defaults to)
15+
TRACE_DEFAULTS = (
16+
("devtools_trace", "devtools_trace", None, True),
17+
("devtools_trace_granularity", "devtools_trace_granularity",
18+
"DEVTOOLS_TRACE_GRANULARITY", "test"),
19+
("devtools_trace_policy", "devtools_trace_policy",
20+
"DEVTOOLS_TRACE_POLICY", "retain-on-failure"),
21+
)
22+
23+
24+
@pytest.hookimpl(tryfirst=True)
25+
def pytest_configure(config):
26+
if os.environ.get("DEVTOOLS_MODE") != "trace":
27+
return
28+
for dest, ini, env, value in TRACE_DEFAULTS:
29+
# Not registered means the adapter is not installed; nothing to configure.
30+
if not hasattr(config.option, dest):
31+
return
32+
if getattr(config.option, dest) is not None or config.getini(ini) not in (None, ""):
33+
continue
34+
if env and os.environ.get(env):
35+
continue
36+
setattr(config.option, dest, value)
Lines changed: 45 additions & 119 deletions
Original file line numberDiff line numberDiff line change
@@ -1,123 +1,49 @@
1-
; DevTools capture, committed with the project.
2-
;
3-
; Why here and not on the command line:
4-
; - a contributor who clones this gets the same capture, without being told
5-
; - a flag has to be retyped every run; an exported variable is per-shell
6-
;
7-
; Run it (no flags — pytest reads this file because it sits beside the test):
8-
; pytest examples/selenium/python-test/trace-py-test/
9-
; pnpm show-trace examples/selenium/python-test/trace-py-test/test-results/*.zip
10-
;
11-
; Override one setting for one run, without editing this file:
12-
; pytest -o devtools_trace_policy=on examples/.../trace-py-test/
13-
; pytest -o devtools_trace_granularity=session examples/.../trace-py-test/
14-
; pytest --devtools examples/.../trace-py-test/ ; live dashboard
15-
; pytest -o devtools=false examples/.../trace-py-test/ ; capture nothing
1+
; DevTools capture, committed with the project: pytest reads this file because
2+
; it sits beside the tests, so a contributor who clones the repo gets the same
3+
; capture without flags or exported variables.
4+
;
5+
; Run it from the repo root:
6+
; pnpm demo:python:pytest ; live dashboard
7+
; DEVTOOLS_MODE=trace pnpm demo:python:pytest ; one archive per failing test
8+
; pnpm show-trace examples/selenium-py/pytest/test-results/*.zip
9+
;
10+
; DEVTOOLS_MODE is this example's switch, not the adapter's: conftest.py turns
11+
; `trace` into devtools_trace = true, devtools_trace_granularity = test and
12+
; devtools_trace_policy = retain-on-failure, each only where nothing else chose.
13+
;
14+
; Override one setting for one run without editing this file:
15+
; pytest -o devtools_trace_policy=on examples/selenium-py/pytest/ ; trace, keep all
16+
; pytest -o devtools=false examples/selenium-py/pytest/ ; capture nothing
1617

1718
[pytest]
1819

19-
; ─────────────────────────────────────────────────────────────────────────────
20-
; devtools — opt in
21-
; ─────────────────────────────────────────────────────────────────────────────
22-
; - capture is ALWAYS opt-in: the plugin auto-loads when the package is
23-
; installed, so it must never change an existing suite's behaviour
24-
; - precedence, highest first: CLI flag > this file > environment
25-
; - environment fallback: DEVTOOLS_ENABLE=1, or DEVTOOLS_PORT=<n>
26-
; - `-o devtools=false` turns it off for one run
27-
; (which is why there is no --no-devtools flag)
28-
; - not set below: devtools_trace already implies it
29-
30-
; ─────────────────────────────────────────────────────────────────────────────
31-
; devtools_trace — a dashboard, or a file
32-
; ─────────────────────────────────────────────────────────────────────────────
33-
; false live mode: streams to a dashboard window (the default)
34-
; true trace mode: writes an archive to test-results/, opens no window
35-
;
36-
; - trace mode is what you want in CI, and when the run already happened
37-
; - open an archive with `pnpm show-trace <zip>`
38-
devtools_trace = true
39-
40-
; ─────────────────────────────────────────────────────────────────────────────
41-
; devtools_trace_granularity — how many archives
42-
; ─────────────────────────────────────────────────────────────────────────────
43-
; session one archive for the whole run (the default)
44-
; test one per test
45-
;
46-
; - a per-test archive holds only that test's own commands, console, network,
47-
; DOM mutations, a11y trees and screencast frames
48-
; - `spec` is not offered: this adapter's spec IS its test file, so it could
49-
; only silently mean one of the two above
50-
devtools_trace_granularity = test
51-
52-
; ─────────────────────────────────────────────────────────────────────────────
53-
; devtools_trace_policy — which archives to keep
54-
; ─────────────────────────────────────────────────────────────────────────────
55-
; Values:
56-
; on keep everything (the default)
57-
; retain-on-failure keep only what failed
58-
; retain-on-first-failure )
59-
; on-first-retry ) accepted, but see below
60-
; on-all-retries )
61-
; retain-on-failure-and-retries )
62-
;
63-
; Those last four currently behave EXACTLY like retain-on-failure:
64-
; - nothing this adapter puts on the wire carries an attempt number
65-
; - so a retried test overwrites its own earlier outcome
66-
; - so the retry-aware question cannot be asked at all
67-
; - the backend logs the degradation rather than pretending otherwise
68-
; - pick one only if you want retain-on-failure under a name that will mean
69-
; more later
70-
;
71-
; What it combines with:
72-
; granularity=test + retain-on-failure -> only the tests that failed
73-
; granularity=session + retain-on-failure -> the whole run, if any failed
74-
; either granularity + on -> everything
75-
;
76-
; As set here, a green run writes nothing at all — which is the point: the
77-
; archives you have are the ones worth opening.
78-
devtools_trace_policy = retain-on-failure
20+
; Capture is always opt-in: the plugin auto-loads wherever the package is
21+
; installed, so it must never change a suite that did not ask.
22+
; Precedence, highest first: CLI flag > this file > environment
23+
; (DEVTOOLS_ENABLE=1 or DEVTOOLS_PORT=<n>).
24+
devtools = true
7925

80-
; ─────────────────────────────────────────────────────────────────────────────
81-
; Environment-only settings
82-
; ─────────────────────────────────────────────────────────────────────────────
83-
; No ini option yet. Export them, or pass the matching keyword to
84-
; devtools.enable() in a plain script (see ../login.py).
85-
;
86-
; Connecting:
87-
; DEVTOOLS_ENABLE=1 opt in (what `devtools = true` does)
88-
; DEVTOOLS_HOST=<host> backend host (default: localhost)
89-
; DEVTOOLS_PORT=<n> attach to a backend already listening
90-
; DEVTOOLS_BACKEND_CMD=<cmd> launch the backend your own way
91-
;
92-
; Capture:
93-
; DEVTOOLS_BIDI=0 skip BiDi; console and network go quiet
94-
; DEVTOOLS_OPEN=0 never open a dashboard window (CI)
95-
;
96-
; Trace mode:
97-
; DEVTOOLS_TRACE=1 selects trace mode, but does NOT opt a pytest
98-
; run in on its own — it is a mode fallback you
99-
; may have exported for your own scripts
100-
; DEVTOOLS_TRACE_GRANULARITY=test ) ambient, so neither turns trace mode
101-
; DEVTOOLS_TRACE_POLICY=... ) on by itself — pair with DEVTOOLS_TRACE=1.
102-
; ) The CLI flag, the ini option and the
103-
; ) enable() argument DO imply it. A run
104-
; ) that ignores one of these says so.
105-
; DEVTOOLS_FILMSTRIP=0 drop the dense screencast from the archive
106-
; DEVTOOLS_A11Y=0 drop the per-action a11y tree and element
107-
; rects (loses the A11y tab and the overlay)
108-
;
109-
; Set for you, not by you — listed so an unexpected value is recognisable:
110-
; DEVTOOLS_RUN_ID one id shared by every process of a run
111-
; DEVTOOLS_RUNNER_CWD where a Rerun spawns (pytest's rootdir)
112-
; DEVTOOLS_APP_REUSE )
113-
; DEVTOOLS_APP_HOST ) point a spawned rerun back at the
114-
; DEVTOOLS_APP_PORT ) backend that asked for it
115-
;
116-
; The same choices in a plain script:
117-
; devtools.enable(
118-
; trace=True,
119-
; trace_granularity="test",
120-
; trace_policy="retain-on-failure",
121-
; filmstrip=True,
122-
; a11y=True,
123-
; )
26+
; Every other setting, for reference (all unset here, so the run is live):
27+
;
28+
; devtools_trace = true|false
29+
; true writes an archive to test-results/ and opens no window; false (the
30+
; default) streams to the dashboard.
31+
;
32+
; devtools_trace_granularity = session|test
33+
; one archive for the run (default), or one per test holding only that test's
34+
; commands, console, network, DOM, a11y trees and screencast frames.
35+
;
36+
; devtools_trace_policy = on|retain-on-failure|retain-on-first-failure|...
37+
; which archives to keep; `on` (default) keeps all. The retry-aware values
38+
; behave like retain-on-failure here: nothing this adapter sends carries an
39+
; attempt number.
40+
;
41+
; A policy or granularity implies devtools_trace = true, except where
42+
; devtools_trace = false is set explicitly at the same level or a higher one
43+
; (CLI > this file); then it does nothing and the run logs a line saying so.
44+
;
45+
; Environment-only: DEVTOOLS_HOST, DEVTOOLS_BACKEND_CMD, DEVTOOLS_BIDI=0,
46+
; DEVTOOLS_OPEN=0, DEVTOOLS_FILMSTRIP=0, DEVTOOLS_A11Y=0. DEVTOOLS_TRACE=1
47+
; selects trace mode for an opted-in run but does not opt one in, and
48+
; DEVTOOLS_TRACE_POLICY / DEVTOOLS_TRACE_GRANULARITY act only in trace mode.
49+
; The same choices in a plain script go to devtools.enable() (see ../scripts/login.py).

0 commit comments

Comments
 (0)