Summary#
Assert.Visible("Confirm") passes on a button that a person cannot press, because something is drawn on top of it.
Assert.Enabled("Save") passes on a button laid out past the edge of its own card. Assert.Visible("Quarterly revenue report") passes on a chip rendering Quarterly rev…, because the DOM holds the whole string whether the box
shows it or not. Every assertion in Assert is about text or state, and none of them can see any of that.
The eleven below ask about geometry: what covers what, what is inside what, what is wider than what, and whether
anything is cut off. They need a renderer that has a font engine and a compositor — so they are checked by
osy test --pixels, which drives a real browser, and under plain osy test they report themselves NOT CHECKED
rather than passing.
Signature#
// is it USABLE?
Assert.Clickable(control); // a click at its own centre reaches IT — nothing covers it, its box is not empty
Assert.Inside(control, container); // its box lies within that container's
Assert.NoOverflow(); // nothing pushes the page wider than the window
// where things ARE
Assert.Above(first, second); // …and Assert.Below
Assert.LeftOf(first, second); // …and Assert.RightOf
// how big, relative to each other
Assert.Wider(first, second); // …and Assert.Narrower
Assert.SameWidth(first, second);
// is any of it cut off?
Assert.FitsOn(control);
// and a photograph, which asserts nothing
Ui.Shot("the empty board");osy test # behaviour. Layout claims report NOT CHECKED — this renderer has no compositor.
osy test --pixels # the same tests in a real browser. The claims are judged; `Ui.Shot` writes PNGs.
osy test --pixels --headed # …and SHOW the browser, slowed down, so you can watch it drive the page.Description#
Why these are separate from every other assertion#
A UI test asserts three different kinds of thing, and only two of them were expressible before:
| the question | the assertion | can a text renderer answer it? |
|---|---|---|
| is this text on the page? | Assert.Visible | yes |
| is this control enabled, checked, selected? | Assert.Enabled, Assert.Checked | yes |
| can a person actually use it? | Assert.Clickable, Assert.FitsOn | no — it needs a compositor |
The third row is not a nicety. A control that renders, satisfies every text assertion, and is unclickable is a shipped bug that a full green suite reports as fine.
NOT CHECKED is not passed#
⛔ A renderer that cannot judge a claim never reports it as holding. Plain osy test renders in happy-dom, which
has no font engine and no compositor: every box it measures is 0×0 and nothing is ever on top of anything. So a
layout assertion there answers NOT CHECKED, with the flag that checks it:
│ Assert.Clickable(…) — NOT CHECKED: this run renders in happy-dom, which has no compositor — every
box it reports is 0×0, so nothing here can judge what covers what. Run the same tests with
`osy test --pixels` to check them in a real browser.The test carries on — its behavioural assertions are real and worth having — and the run keeps the count, so a green tally can never read as "the layout was checked".
⚑ This is why one test file runs in both tiers. The alternative — refusing — would make a .test.osy containing
a layout assertion runnable under --pixels and not under osy test, which is a worse product than having no tier
at all. You write one file; you choose the renderer at the command line.
Assert.Clickable — the one to reach for first#
It resolves the control exactly as Ui.Click would, takes the point a click would land on, and asks the page what
is actually there. Three ways it refuses, each with the numbers:
- something is covering it — the refusal names what a press would hit instead;
- its box is empty (0×0) — in the DOM, taking up no space, pressable by nobody;
- it is laid out off the window entirely.
Assert.Clickable(…) failed: a click at 'Confirm's own centre (68, 219) lands on <span> "half price
today" instead — something is covering it. That is what a person pressing it would hit, so the control
is on screen and unusable.Assert.FitsOn — the claim no text assertion can make#
textContent reads the whole string whether the box shows it or not, so a truncated label is invisible to every
other assertion in the language. This is the only one that can see it:
Assert.FitsOn(…) failed: 'Quarterly revenue report for the northern region' has content 257px WIDER
than its box (box 90×24, content 347×24), so part of it is CUT OFF on screen. It reads "Quarterly
revenue report for the northern region" to the DOM, which is why every text assertion passes on it.No pixel counts, deliberately#
⛔ There is no Assert.Equal(200, <the card's width>), and that is the design rather than an omission. An
absolute pixel breaks when a font ships, a browser rounds differently, a theme token moves by 2px, or the display
changes scale — and a noisy suite gets deleted, taking the good assertions with it.
Every assertion here is an invariant: it compares two things on the same screen, so it survives a restyle, a font
update and a browser version. Assert.Wider(a, b) still holds after you change every size in the theme;
Assert.Equal(200, …) does not.
Assert.Above is not Assert.Before#
Ui — drive the app's UI from a test's Assert.Before is about document order among the rows of a list, and it holds in either
renderer. These are about where things are. The two disagree exactly when the app reorders visually — a
column-reverse, a CSS order:, grid placement, absolute positioning — which is the case where the page reads one
way and the markup says another, and only these can see it.
What a name refers to#
A locator names the thing that reads the words, not the box around it — the same rule every locator in
Ui — drive the app's UI from a test follows. So Assert.Wider("Sidebar", "Rail") compares the two pieces of TEXT, and a 300px panel with
a short label measures as its label. That is correct and surprising, so the refusal prints the tag it measured and
names the fix: give the container a label: and name that, exactly as within: names one.
Box(label: "Sidebar panel", w: 300) { Text("Sidebar"); }
Assert.Wider("Sidebar panel", "Rail panel"); // the PANELS — not the words inside themUi.Shot — evidence, not an assertion#
Ui.Shot("label") photographs the page. It asserts nothing and can never fail a test: under --pixels it writes a
PNG beside your project, and under plain osy test it records "no image: this run renders in happy-dom, which has
no compositor" and carries on. It never writes something that is not a photograph under a photograph's name.
Reach for it when you want to LOOK at what a test is driving — it is the fastest way to understand a layout failure, and often faster than reading the assertion that caught it.
Running the pixel tier#
osy test --pixels runs the same tests in a real browser instead of happy-dom. It is opt-in because it costs a
browser: the tier needs Playwright and a Chromium (or your system Chrome, which it prefers). The check runs before
anything is compiled or booted, so a machine that cannot run it is told in a second, with the command that fixes
it — never a stack trace a minute into a run.
Everything else is identical: the same locators, the same refusals, the same test file.
Add --headed to watch it: the browser is shown and each interaction is slowed down enough to follow, which is
usually the fastest way to understand why a locator refused. It is only meaningful beside --pixels, and says so
rather than being quietly ignored.
Examples#
A page carrying the two shapes these exist for — a button something is drawn over, and a label too long for its box
— and the tests that catch both. Every assertion here passes in a real browser except the two that are meant to
fail, and every one of them reports NOT CHECKED under plain osy test.
using Osyrin.Ui;
[Composable]
component Clipped(string text = "") {
variants {
base { W = 90; Overflow = Overflow.Hidden; WhiteSpace = WhiteSpace.Nowrap; TextOverflow = TextOverflow.Ellipsis; }
}
render { Text(text); }
}
[Page("/checkout")]
[AllowAnonymous]
[Render(CSR)]
component CheckoutPage() {
action Nothing() { }
render {
Stack(gap: 4, p: 6) {
Text("Masthead");
Card("Actions") { Button("Cancel", onPress: Nothing); }
// A banner drawn over the button: on the page, enabled, correctly labelled, and unpressable.
Box(position: Position.Relative) {
Button("Confirm", onPress: Nothing);
Box(position: Position.Absolute, top: 0, left: 0, w: 400, h: 60, z: 50) { Text("half price today"); }
}
Clipped("A label far too long to fit inside the box it was given");
Text("Footer");
}
}
}
[Test]
void the_cancel_button_is_usable() {
Ui.Visit("/checkout");
Assert.Clickable("Cancel"); // nothing covers it, and its box is not empty
Assert.Inside("Cancel", "Actions"); // it has not been laid out past its own card
}
[Test]
void the_page_reads_the_way_it_is_laid_out() {
Ui.Visit("/checkout");
Assert.Above("Masthead", "Footer");
Assert.Below("Footer", "Masthead");
Assert.NoOverflow();
Ui.Shot("the checkout page"); // asserts nothing — a PNG under `--pixels`, a note without it
}See also#
- Ui — drive the app's UI from a test — the verbs and the text/state assertions these sit beside
- Assert — the general assertion vocabulary
- Running tests locally — running these, and what
--pixelsneeds - layout primitives —
gap,alignandjustify, which decide the geometry these measure - style props — the style props, including the three a truncated line needs