Part of #277
Split out of #289, which bundled two capabilities: making the dashboard's run
controls work at all, and preserving a run to diff against a rerun. This is the
first half, and it is the larger one — #289 keeps the preserve-and-diff scope and
now depends on this.
RUN_CAPABILITIES in constants.py hard-codes every control off:
RUN_CAPABILITIES = {"canRunSuites": False, "canRunTests": False, "canRunAll": False}
so Run / Rerun / Run-all are refused outright, and the adapter sends neither a
launch nor a rerun command. Its own comment says to revisit this with the
Preserve-and-Rerun work.
runner.ts already has a non-wdio launch path — #spawnGeneric runs an
arbitrary command from payload.rerunCommand, falling back to launchCommand —
which is what Nightwatch and Selenium-JS use.
packages/selenium-devtools/src/rerunManager.ts is the closest prior art: it
captures the launch command from argv and builds a rerun template carrying a
per-runner filter flag (--grep, --testNamePattern, -t, --name). Python
needs one runner, and pytest's selector is not a flag but a nodeid
(file.py::Class::test), which is exact rather than a name match. It must also
strip an inherited selector from argv the way FILTER_FLAG_ALIASES does, or a
second rerun stacks selectors.
Corrections to this issue, from implementing it
Two claims written here before the work started turned out to be wrong.
"The backend needs no changes" is wrong. #resolveGenericCommand
substitutes its only slot, {{testName}}, through escapeFilterRegex, because
every runner that consumes it filters by regex (mocha --grep, jest
--testNamePattern, cucumber --name). A pytest nodeid is matched literally,
so the escaped form selects nothing: measured, pytest 'test_thing\.py::test_a'
collects nothing and exits 0, which reads as a rerun that ran and passed.
The backend needs a second slot that is substituted verbatim — filled from the
payload's uid, shell-quoted, never escaped.
The "known constraint from #297" below is wrong. A rerun does not travel
down the worker socket at all: POST /api/tests/run spawns a fresh process, and
the socket carries only clientConnected / clientDisconnected. The single
workerSocket slot therefore affects which process the dashboard's state
belongs to under pytest -n, not where a rerun is routed — and a rerun is
spawned as a single process regardless. Superseded text kept below for history.
Rerun requests travel OUTBOUND down the backend's single workerSocket, and
workerSocket = socket keeps only the most recent connect. Under pytest -n
every worker plus the controller connects, so a rerun request reaches whichever
process connected last rather than the one owning the test. Either handle it or
document it; do not discover it in #289.
Acceptance criteria
A failing test can be rerun from the dashboard for both pytest and a plain
script, or the control is refused with a stated reason where it cannot be
serviced. The run capabilities the adapter advertises match what it can actually
do. A test asserts the rerun command built for a pytest nodeid, and that a second
rerun does not stack selectors.
Depends on #282 and #287 (both closed).
Part of #277
Split out of #289, which bundled two capabilities: making the dashboard's run
controls work at all, and preserving a run to diff against a rerun. This is the
first half, and it is the larger one — #289 keeps the preserve-and-diff scope and
now depends on this.
RUN_CAPABILITIESinconstants.pyhard-codes every control off:so Run / Rerun / Run-all are refused outright, and the adapter sends neither a
launch nor a rerun command. Its own comment says to revisit this with the
Preserve-and-Rerun work.
runner.tsalready has a non-wdio launch path —#spawnGenericruns anarbitrary command from
payload.rerunCommand, falling back tolaunchCommand—which is what Nightwatch and Selenium-JS use.
packages/selenium-devtools/src/rerunManager.tsis the closest prior art: itcaptures the launch command from argv and builds a rerun template carrying a
per-runner filter flag (
--grep,--testNamePattern,-t,--name). Pythonneeds one runner, and pytest's selector is not a flag but a nodeid
(
file.py::Class::test), which is exact rather than a name match. It must alsostrip an inherited selector from argv the way
FILTER_FLAG_ALIASESdoes, or asecond rerun stacks selectors.
Corrections to this issue, from implementing it
Two claims written here before the work started turned out to be wrong.
"The backend needs no changes" is wrong.
#resolveGenericCommandsubstitutes its only slot,
{{testName}}, throughescapeFilterRegex, becauseevery runner that consumes it filters by regex (mocha
--grep, jest--testNamePattern, cucumber--name). A pytest nodeid is matched literally,so the escaped form selects nothing: measured,
pytest 'test_thing\.py::test_a'collects nothing and exits 0, which reads as a rerun that ran and passed.
The backend needs a second slot that is substituted verbatim — filled from the
payload's
uid, shell-quoted, never escaped.The "known constraint from #297" below is wrong. A rerun does not travel
down the worker socket at all:
POST /api/tests/runspawns a fresh process, andthe socket carries only
clientConnected/clientDisconnected. The singleworkerSocketslot therefore affects which process the dashboard's statebelongs to under
pytest -n, not where a rerun is routed — and a rerun isspawned as a single process regardless. Superseded text kept below for history.
Acceptance criteria
A failing test can be rerun from the dashboard for both pytest and a plain
script, or the control is refused with a stated reason where it cannot be
serviced. The run capabilities the adapter advertises match what it can actually
do. A test asserts the rerun command built for a pytest nodeid, and that a second
rerun does not stack selectors.
Depends on #282 and #287 (both closed).