Repository navigation
shotbox mutate runs a browser test against small changes to its page,
to find a test that breaks on one that shouldn't matter or passes on
one that should. wait node waits for a node's state, value or count,
and wait gone for one to go; wait idle waits until each program
answers, free on its main thread. capture --tree saves the tree
beside a picture, so compare says what each change is in;
--ignore-node leaves out a clock by its name, and compare --update
takes a reviewed change in one command. a11y diff says what changed
between two trees, and shoot waits for a node or an announcement.
record --sound keeps picture and sound in step on X11. A session's
end now stops what it started that left its process group, and a11y lint and contrast fail, rather than pass, when a program doesn't
answer the accessibility bus: see the changes to check.
Changes to check when upgrading
- A session's end stops what it started that left its process
group, assetsiddoes (a daemon, a test runner's helper, a
browser's own launcher), as a session killed outright already did.
That coversshotbox run,exec,shoot,stop, MCP's
stop_session, Python'sSessionand each run ofshotbox mutate.
It also stops a lasting session started from inside it (shotbox startwithinshotbox run). Such a program kept running after the
session ended. A recording's keeper finishing its file is spared, and
so is a command put in the session from outside with--session. - A program that doesn't answer the accessibility bus (busy, or hung)
fails a check rather than passing it.a11y lintanda11y contrastwithout--windowfail with status 2,not all checked
(Python's raise, MCP's say so as an error), rather than finding
nothing wrong. Everya11yandwait nodecommand that gives up adds
; program 1234 didn't answer: busy, or hungto its error, which
alone read as a window, node or focus not being there. Each gives
such a program its--timeout(15 s at most) to answer, not
libatspi's 15 s:treeandlinttook 16 s with--timeout 2, and
witha11y's default--timeoutof 10, a program slower than that to
answer now counts as not answering.
Added
shotbox wait node RE --state STATE(enabled, focused, checked,
expanded, any the tree says, or not-STATE),--value V(a field's
text, a progress bar's percent) and--count Nwait for a node in
that state, with that value, or that many of them;shotbox wait gone REuntil no showing node matches: a spinner, a dialog after OK. Each
says what it saw last when it gives up. Python'swait_node(states=, value=, count=)andwait_gone, MCP'swait_fornode and gone.--ignore-node NAMEleaves out a node by its name, where there's a
tree:compare's from the tree beside either picture,wait matchand
wait change's from the live one as the wait starts; a clock or a
spinner needn't be given as coordinates that drift with the layout.
Python'swait_matchandwait_changetakeignore_node, MCP's
wait_forignore_node.wait match --click [X,Y]clicks where the picture matched, at its
middle or at X,Y in it, for a canvas or a game with no tree to click
by; Python'swait_match(click=), MCP'swait_formatch withclick.shotbox capture --tree(with--a11y) andshoot --treewrite the
accessible tree beside the picture,OUT.json, its bounds in the
picture's pixels;compare, given pictures with one, says what each
part that differs is in, on--diff's page too:412 pixels, in 80x24 at 12,40: button "Save", and a change in text GTK 4 draws partly
outside its widget is named by that widget, not the panel round it.
--updatetakes the tree with the picture, and removes the
reference's own if the new picture has none. Python'scapturetakes
tree=True.- A recipe, in docs/recipes.md: a web app in Chrome, served
once in a lasting session and pictured as many times as wanted, with
the flags it needs and why, and what it doesn't. shotbox compare REFDIR NEWDIR --updatemakes each new picture that
fails the reference, and adds each with no reference yet, leaving those
that pass untouched: a reviewed change is one command.--diff DIR
also writesindex.html, each failure's reference, new picture and
diff side by side, with copies of both sides, so the directory alone is
a CI artifact.shotbox wait stable --crop Gwatches just that part, and--ignore G(repeatable) leaves out a box, so a page with a clock or a live
chart can hold still round it;shoot --wait stablewatches its
--crop, and takes--ignoretoo. Python'swait_stableand MCP's
wait_forstable takecropandignore.shotbox shoot --wait node:REand--wait announce:REwait, in
order with the other waits, for a showing node with RE in its name or
text, or for something said to a screen reader, aswait nodeand
wait announcedo: a one-shot picture of a page once a line says
it's drawn. Either gives the session--a11y.shotbox a11y diff BEFORE [AFTER]says what changed from a tree
saved witha11y tree --jsonto another, or to the live one, as
MCP's actions withchangessay it: status 1 if anything did, 0 if
nothing, 2 if it couldn't tell.shotbox wait idle [--window RE]waits until each program with a
window shown has answered just now, three times in a row: free on its
main thread, whichwait stablecan't tell, as a busy program holds
as still as an idle one. It asks by X11's_NET_WM_PING, and with
--a11yGTK programs on the accessibility bus too, on Wayland as well;
a browser's page, and a window nothing can ask, are said on stderr not
to have been checked. Python hasScreen.wait_idle(), and MCP's
wait_foridle.shotbox mutateruns a browser test against small changes to the page
it tests, each run in a session of its own, to find a test that fails
on a change that shouldn't matter (BRITTLE: the page at another size,
a button moved 40 px) or passes on one that should (BLIND: a button
hidden or dead, a total a cent off). The test opts in withawait mutated(context)from the npm package; each change to a target is
proven by Playwright looking for it as the test would, and a run whose
change can't be proven is UNAPPLIED, with why. Python has
shotbox.mutate().
Fixed
record --soundon X11 drifted from the sound on a busy machine, a
tone up to a tenth of a second or more from its flash, and by
different amounts within one file: ffmpeg's x11grab took the frames.
shotbox takes them itself now, straight from the X server, with the
pointer drawn on for--pointer, and hands them to ffmpeg raw, as
wf-recorder's are on Wayland.record --soundon Wayland lost the first second or so of sound:
ffmpeg opened the sound only once it had looked over the first frames.- With
--usage, a recording still finishing its file as its session
ended was cut off, leaving an empty or unfinished file and no error:
the session's slice was stopped with the recording's keeper in it.
The keeper, and what it started, now go to a scope of their own
first, and finish as they do without--usage. - With
XDG_RUNTIME_DIRset to a directory that isn't there, as after a
logout, or one you can't make a directory in, as undersu,start,
stopandsessionsended in a traceback. They now say so:
XDG_RUNTIME_DIR is /run/user/1000, which isn't a directory: unset it, or point it at your runtime directory, orcan't make /run/user/1000/shotbox: Permission denied. a11y clickcouldn't click a Firefox check box inside its<label>
by name, with the pointer or--action: Firefox puts a label of the
same name round it, and each ruled the other out. Nor a Chromium field
named by a<label for>, whose text in the label matched too. The
field is clicked.- The focus renamed just as its window came up ("Check" to "Checked")
was sometimes not announced under load, sowait announcefor it
gave up: the recorder looked at the focus again after the rename but
before its event, and took the new name for the one it already had.
A focus first looked at that late can still be missed. - With
--failedand without--a11y,wait announceand Python's
wait_focus, giving up, said the screen twice (the screen then: F; the screen then: F) and took the--failedpicture twice. Once now. - MCP's actions with
changes, andshotbox.a11y.changes(), said
nothing of a program that didn't answer as the tree was read before,
only after: what changed in its windows was left out unsaid, and
could read as "Nothing changed". It's said either way.