Repository navigation
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/--heightsay otherwise. - The built-in face alone is used, so screenshots match across machines (
--fonts systemopts out). - The clipboard is private to the process.
| 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) |
| 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 |
| 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
|
| 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 |
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.
Getting started
What works today
Measured
Design decisions
Testing and tools