Skip to content

Scripting the shell

codingncaffeine edited this page Oct 4, 2026 · 19 revisions

Scripting the shell

sashfold --script <file> [--update-goldens] [--width N] [--height N] [--theme <file.json>] [--downloads <dir>]

--script replays a text file of commands against the shell, headless, and checks its assertions. It is the shell's test harness (the same script runs on every OS in CI and its screenshots are byte-identical) and the proof that a browser can be driven, and read back, from a text file.

  • One command per line; # starts a comment.
  • Relative paths resolve against the script's own directory.
  • Coordinates are window coordinates; the window is 1024×720 unless --width/--height say otherwise.
  • The built-in face alone is used, so screenshots match across machines (--fonts system opts out).
  • The clipboard is private to the process.

Navigation

Command Does
open <url|path> Navigate; a bare path becomes a file: URL
navigate <typed> Address-bar semantics (https-first for a bare host)
back, forward, reload History
new-tab, close-tab [n], select-tab <n>, duplicate-tab [n] Tabs; key ctrl+shift+t brings the tab closed last back
resize <w> <h> Resize the window (device px)
scale <factor> The display's device px per CSS px from here on, as a window on a scaled display reports it: the chrome and the pages draw at the factor, scripts see it as devicePixelRatio
tick Let queued work run
advance <ms> [<times>] Move the pages' clock and run the timers due, that many times over (a chain of timers each set by the last needs the steps)

Input

Command Does
click <x> <y>, middle-click <x> <y> A click at window coordinates
click-text <text> Click the center of the first run containing the text (one run: a single word is safest)
click-text-nth <n> <text> The same for the nth run holding the text, from 1, in the page's order: for a page that says a word more than once
ctrl-click-text <text>, ctrl-shift-click-text <text> The same with Ctrl held: a link opens in a tab of its own, behind the page; with Shift as well, in front of it
right-click <x> <y>, right-click-text <text> A right click: the menu of what is there
shift-right-click <x> <y>, shift-right-click-text <text> The same with Shift held: the shell's menu whatever the page says
right-click-tab <index>, right-click-address, main-menu A right click on that tab or on the address bar; a click on the main menu's button
menu <label> Choose that item of the open menus, the innermost first: it runs, or its submenu opens
menu-hover <level> <row>, menu-click <level> <row> The pointer over, or a press on, that row of that open menu (the outermost is 0; a separator is a row)
press <x> <y> [<button>], release [<button>] The two halves of a click, for a button held while the pointer moves (1 left, 2 middle, 3 right); the release lands where the pointer was last moved
set-clipboard <text> Put text on the clipboard, which a script keeps private to the process
move <x> <y> Hover
wheel <notches> Scroll the content (positive rolls up)
wheel-at <x> <y> <notches> The same, over one window point — the box under it takes it if it can
scroll-pixels <dy> Move the content at the window's center by that many device px, as a finger's drag does (positive brings what is below into view)
type <text> Type into whatever has focus
preedit <text> An input method's composing text at the caret of the focused field, underlined; no text clears it
assert-text-input on|off Whether a field has a caret an input method would be told about
window-controls on|off The frame is the shell's to draw, as on a compositor without server-side decorations
assert-window-request <what> What the last press asked of the window, and takes it: none, move, minimize, maximize, close, or resize-<edge> (top, bottom-right, …)
window-active on|off Whether the window is the one in front, as the system says when another takes its place
assert-theme-problem none|<text> What the theme in use could not put on — a picture it names that is not there — holds the text, or is nothing
import-theme <path> A Firefox or Chrome theme — its folder, its .xpi, its .crx, named relative to the script — converted and put on, as one dropped into the profile's themes folder is
key <chord> A key, e.g. Enter, Escape, ctrl+l, alt+left, ctrl+shift+tab, f12, menu, shift+f10
drag <x1> <y1> <x2> <y2> Press, move, release the left button
focus <name> Focus the first form control with that name
select-text <text> Select the first run's occurrence of the text
reader Toggle reader mode
inspect-text <text> Devtools: inspect the element of the first run containing the text

Assertions

Command Checks
assert-url <url>, assert-address <text>, assert-title <text> The current page
assert-address-ends <text> The end of the address: a page opened from a file has the machine's own path in front of it
bookmark-page Ctrl+D: the page in front onto the bookmarks bar, or its bookmark taken away
assert-bookmarks-bar <titles> The bar as it is shown, left to right, joined by |: a folder in [ ], then » when some did not fit; nothing after the verb when the bar is not shown
press-bookmark <n>, middle-press-bookmark <n>, right-press-bookmark <n> A press on the bar's nth bookmark, from 1; one past the last shown is the chevrons
import-bookmarks <file> A bookmarks file of another browser, named relative to the script
bookmark-sources-home <folder> The home folder other browsers' bookmarks are looked for under by about:bookmarks, named relative to the script: a made-up one — without the verb nothing is looked for, and a script never reads the reader's own
bookmark-backups [<folder>] A folder for the dated copies of the bookmarks: one of the system's temporary ones, named for the script and emptied first, then filled with the files of the folder named
downloads-folder What the shell saves goes into a temporary folder named for the script, emptied first, so that a script which writes a file finds the folder the same on every run
assert-header-paints <n> Since mark: the frames that drew the header alone — a theme's picture had moved and nothing else changed
window-visible on|off Whether any of the window can be seen, as a compositor says of one that is minimized or covered
assert-text <text>, assert-no-text <text> Text present or absent on the page
assert-status <text> What the shell has to say — where a link leads, a notice, Done — whether or not it is showing: Done is never shown
assert-tabs <n>, assert-scroll <n>, assert-scrolled Tabs and the page's scrolling
assert-tab-titles <A | [B] | C> Every tab's title in the strip's order, the tab in front in brackets, a pinned one with ^ before it
assert-closed-tabs <n> How many closed tabs could come back
resize-through <w> <h> [<w> <h> …] The sizes a window being dragged goes through between two frames, each reported to the shell as it comes, then the one frame: the tab in front is laid out once, for the last
themes-gallery <file> about:themes reads Firefox's themes from this file (named relative to the script, in the shape of the add-ons site's search answer) in the catalogue's place: a script never asks the network
themes-folder Gives the shell a themes folder of the reader's own (a temporary one, emptied first), as the window gives it the profile's: a theme the script downloads is then converted and put on
assert-box-scroll <x> <y> <dx> <dy> How far the box that scrolls under that window point has moved its content
assert-pixel <x> <y> <#rrggbb> One pixel of the frame
assert-focus address|page Where keyboard focus is
assert-value <name> <text> A control's current value
assert-focused <name> The focused control ("" for none)
assert-selection <text> The selected text (one line)
assert-find <text> The find bar's count: 3 of 12, No matches, "" when closed
assert-hints <n> The number of link-hint labels showing
assert-menu <items> The innermost open menu, its items joined by |: a separator is -, an item that cannot be chosen is in parentheses, a checked one follows *, one with a submenu precedes >; nothing when no menu is open
assert-menus <n> How many menus are open, submenus counted
assert-clipboard <text> What the clipboard holds
assert-inspected <tag#id.class> The inspected element
assert-golden <png> The frame's bytes equal the file; --update-goldens blesses
mark Records the restyle, relayout and paint counters as they stand; the assertions below read what changed since (the counters themselves are read, never set)
assert-restyles <n> The number of restyles since the last mark
assert-relayouts <n> The number of relayouts since the last mark
assert-paints <n> The number of frames painted since the last mark
assert-pictures <n> The number of the page's pictures decoded so far
assert-fonts-asked <n> The number of declared fonts the page's text has needed, and so fetched, since the last mark

Output

Command Does
screenshot <path> Write the frame as a PNG
echo <text> Print a line
print-pixel <x> <y> Say what the frame holds there; never fails
find-pixel <#rrggbb> [<x> <y> <w> <h>] Say where the frame is exactly that color, in the box or anywhere: the first few places and how many in all; never fails

The scripts in the repository

tests/shell/ holds smoke.script, forms.script, selection.script, find.script, reader.script, hints.script and devtools.script, each with its goldens under tests/shell/goldens/, all run by CI; and live.script, which needs the network (Wikipedia, Hacker News, a real link click, example.org from the cache, badssl.com's certificate page, a Wikipedia search through a form, and Acid1) and is run by hand.

Clone this wiki locally