Osy#the first language built for agents
Agents firstAgentic appsWorkflowsDurable Execution — built inSecurityTestingThe editorThe UI modelOne program

Reference / Testing

Layout assertions — is it actually usable on screen?

Assert.Clickable · Assert.Inside · Assert.NoOverflow · Assert.Above/Below · Assert.LeftOf/RightOf · Assert.Wider/Narrower · Assert.SameWidth · Assert.FitsOn · Ui.Shot · osy test --pixels

Every other assertion is about TEXT or STATE, and all of them pass on a screen that is visually broken: a control can be on the page, enabled, correctly labelled, and still be under an overlay, off its own card, or too narrow for its own label. These eleven ask about GEOMETRY instead. They need a renderer with a compositor, so they are checked by `osy test --pixels` (a real browser) and report themselves NOT CHECKED under plain `osy test` — never green. `Ui.Shot("label")` photographs the page beside them.

preview1 example compiled by CItestinguilayout

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 questionthe assertioncan a text renderer answer it?
is this text on the page?Assert.Visibleyes
is this control enabled, checked, selected?Assert.Enabled, Assert.Checkedyes
can a person actually use it?Assert.Clickable, Assert.FitsOnno — 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 them

Ui.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#

Related

Ui — drive the app's UI from a test

Drive the real UI from a test: navigate to a route, click what a person would click, and assert on what the screen…

Assert

The assertions a test makes. Beyond the usual equality and null checks there are comparisons (Greater, Less, InRange)…

Running tests locally

Runs your app's tests against a Platform on your own machine — no account, no network, no setup beyond a running local…

layout primitives

The built-in layout primitives and how they arrange children. `Stack` stacks children in a column, `Row` lays them in a…

style props

Inside a `variants` block, each `Name = value` is a style prop from a fixed vocabulary the renderer maps to CSS — paint…