Skip to content

API Reference telatest

Andres Felipe Martinez Salazar edited this page Nov 2, 2026 · 9 revisions

API reference: package telatest

import "github.com/kuronosu/tela/telatest"

This page is generated from the doc comments of the package; the same text is on pkg.go.dev. For guides and examples, start at the wiki home.

Index

Overview

Package telatest tests Tela UIs without a window: it mounts a tree in a headless window, simulates a user with real pointer and key events, finds elements by role and text like Playwright, and compares the pixels with goldens (ARCHITECTURE section 17).

func TestCounter(t *testing.T) {
    app := telatest.Mount(t, Counter(), telatest.Size(400, 300))
    app.Click(telatest.ByRole("button", "Increment"))
    app.ExpectText("Count: 1")
    app.Golden("counter.png") // testdata/counter.png; TELA_UPDATE=1 regenerates
}

Locators

ByRole, ByText and ByClass describe an element by what a user (or an assistive technology) sees, resolved against the semantics tree each time they are used; ByTestID finds the element that carries an h.TestID, for those a user cannot name (like data-testid, it is not in the aria snapshot). A locator that matches none or several elements makes an action fail with the aria snapshot and the closest names; narrow it with Locator.Filter and Locator.Nth. Hidden elements are never matched.

Actions and auto-wait

App.Click (with Right for the secondary button and Delay for a held one), App.DoubleClick, App.Tap, App.LongPress, App.Hover, App.Focus, App.Scroll, App.Drag, App.DragBy, App.Press, App.Type and App.Fill act like a user. The pointer actions move the pointer to the element's center and send pointer events through hit-testing and the gesture arena (hover and :active states, focus on press, click counts, drags); Tap and LongPress use a touch pointer. The keyboard actions send key events to the focused element (typing into a focused text field; Fill replaces a field's value like Playwright's fill).

Before acting, an action waits until the element is visible, enabled, in view and not covered by another element. Before and after, the app is settled: pending state updates are applied and frames run, advancing the app's clock to each one, until nothing is dirty and no animation or scroll fling is running: transitions (hover:, focus:, dark: changes with transition classes) and finite animations run to their end, so a screenshot after an action shows the settled state. Infinite animations (animate-spin, animate-pulse) are never waited for, and use App.Advance to step them. An app that never goes quiet otherwise (a goroutine that updates state all the time) is not waited for either: the first time it is logged, and actions then run only a few frames. Both are decided in app time (Timeout of it), so a slow machine never cuts a finite animation short.

App.Advance moves the clock by hand: it is how a test waits for a long-press timer, an animation or a fade. To capture the frames of an animation, mount with NoSettle: actions, screenshots and the queries (App.AriaSnapshot, App.Count, App.Bounds, App.Stats, App.Cursor) then leave running animations where they are, and only Advance moves them (ADR-097).

The expectations (App.ExpectText, App.ExpectNoText, App.ExpectRole, App.ExpectCount, App.ExpectFocused, App.ExpectValue, App.ExpectAria and App.Expect) retry until they hold or the Timeout expires, letting a frame of app time and a moment of real time pass between tries, so state set by a goroutine arrives (under NoSettle the first try does not move the clock: it handles only the work due). App.Expect checks the states of an element (Checked, Disabled, Expanded, Selected, Pressed, Focused, HasName, HasValue) without matching strings of the aria snapshot.

App.ClickAt and App.DragFromTo act at window coordinates, for what a locator cannot name; App.Bounds returns an element's box as a tela.Rect. App.PixelAt reads the frame, App.Measure returns the work the tree did during some actions, and MountFunc is Mount for one component. MountApp mounts a tela.App's Root in the app's own window (its size, root class, color scheme, theme and class aliases, and the fields of the OS window: Title, size limits, Frameless, Hidden, Placement, OnClose...), the App that tela.Main runs and tela snapshot reads from the package; FromApp gives the settings that shape what is drawn to Mount, Render and the other entry points.

Windows

An app has one window per tela.Window that is mounted, besides its main window. The headless backend plays a fake window manager (ADR-179): it grants every request at once, gives hidden and minimized windows no frames, and records parents, modality, hit regions and caption presses. The app still builds and runs the effects of a hidden or minimized window, as on a desktop (ADR-185): a tray item clicked while the main window is hidden updates its state, its tray menu and the windows it opens; settling waits for that work, and the window draws it when shown.

An App is scoped to one window. Mount returns the one of the main window; App.Windows lists the open windows (in the order they opened, the main window first), App.Window selects one by title, App.WindowAt by index and App.LastWindow is the newest, and App.WaitForWindow runs a step and returns the window it opened, like Playwright's context.pages() and page.waitForEvent('popup'). Each returns an App with the same queries, actions, expectations, goldens and pixels as the main one, working on that window: it is always the same App for the same window.

settings := app.WaitForWindow(func() {
    app.Click(telatest.ByRole("button", "Settings"))
})
settings.ExpectText("Preferences")
settings.Golden("settings.png")

Selecting a window waits for it to open, like a locator waits for its element; the clock, settling and the end of the app belong to the whole app, so an action in one window waits for the work it causes in the others. A pointer press or a key in a window that is not the active one activates it first, as the user clicking or typing there does.

The window itself: App.Title, App.WindowState (tela.WindowState, as UseWindow sees it), App.WindowSize, App.Placement, App.Modal, App.Parent, App.Blocks, App.BlockedBy and App.Closed; the expectations App.ExpectTitle, App.ExpectWindowState (with Maximized, Fullscreen, Minimized, Hidden, Active and ClientDecorations), App.ExpectClosed, App.ExpectWindowCount and App.ExpectNoWindow. What the user does with the window manager: App.RequestClose (the close button), App.Minimize, App.Maximize, App.Unmaximize, App.SetFullscreen, App.Restore, App.Move, App.Resize and App.BringToFront. A window blocked by a modal one drops input.

Custom title bars: App.HitRegions and App.HitAt read the regions h.WindowDrag and h.WindowControl send to the OS, a click on the drag area (App.Click, App.ClickAt, App.DoubleClick) is the OS's press (App.CaptionDrags, App.WindowMenus, a double click maximizes), and the controls are buttons that act on click.

Quitting: App.ExpectQuit waits for tela.Quit or a close request of the main window without an OnClose; the app is then over, as when tela.Main returns: every window unmounted, the cleanups of the effects run, and further use fails the test. App.HasQuit and App.ExpectRunning check that it did or did not. The window options (Title, MinSize, MaxSize, FixedSize, Frameless, AlwaysOnTop, TitleBar, Backdrop, StartHidden, Icon, Placement, OnClose, Screen) set the main window's fields for Mount.

Drag and drop

The headless backend plays the operating system's part in HTML drag and drop. App.DragTo drags an h.Draggable element to another element, App.DragToWindow to one in another window of the app, and App.DropOutside ends a drag in another application; App.DropFiles and App.DropData drop what another application drags (build it with DragFiles, DragText, DragHTML, DragPNG, DragType). They return the effect the app accepted, tela.DropNone for a refusal. The steps they are made of can be used alone, to test drag-over styling or inspect the data: App.StartDrag, App.HoverDrag, App.LeaveDrag, App.CancelDrag, App.DropOn and App.ActiveDrag; the options Carrying, Allowed, Modifiers and Steps shape them.

app.DropFiles(telatest.ByTestID("upload"), filepath.Join(t.TempDir(), "photo.png"))
app.StartDrag(telatest.ByTestID("card-7"))
app.HoverDrag(telatest.ByTestID("column-done"))
app.ExpectText("Release to move here")
app.DropOn(telatest.ByTestID("column-done"))

System settings

App.SetDarkMode, App.SetReducedMotion, App.SetHighContrast, App.SetTextScale, App.SetAccentColor and App.ClearAccentColor change what the system says while the app is mounted, as the user does in the system's settings; the components that read the setting rebuild. The options Dark, ReducedMotion, HighContrast, TextScale and AccentColor set it at Mount.

PlatformKeys gives the app another platform's key conventions (the text fields' bindings, the events' CmdOrCtrl, what "ControlOrMeta" presses), so a test of Command shortcuts runs on any OS.

The clock

The window's clock is manual: it starts at zero and moves only when the app waits or the test advances it, so animations, scroll flings and timers are deterministic. Frames are paced like a 60 Hz display.

Goldens and snapshots

App.Golden compares the window's pixels with testdata/<name>, bit by bit unless a Tolerance is given (Tela renders the same pixels on every platform, ADR-027); App.GoldenScales makes one golden per display scale. TELA_UPDATE=1 go test regenerates goldens (ADR-022). App.AriaSnapshot returns the YAML that tela inspect prints, and App.ExpectAria and App.AriaGolden compare it.

Debug overlays

Debug turns on the overlays of TELA_DEBUG (layout, paint-flash, layers, fps, semantics) in a mounted app or a headless render, so a golden shows them. The environment variable itself is ignored: a test only gets overlays it asks for. A paint flash fades over 300 ms of app time and the fps meter redraws on every frame; neither keeps actions waiting, so use App.Advance to look at them, and DebugClock to script the frame times the meter shows.

Headless rendering without a test

Render, AriaSnapshot and LayoutJSON mount a tree, run frames until it is quiet and return the image, the aria snapshot or the layout JSON, for tools. tela snapshot and tela inspect are built on them.

Constants

const (
    // HitClient is ordinary content: what lies outside every region, and
    // what a hole in a drag region is.
    HitClient = platform.HitClient
    // HitCaption is a drag area (h.WindowDrag): the OS handles presses in
    // it (move, double click to maximize, window menu).
    HitCaption = platform.HitCaption
    // HitMinimize, HitMaximize and HitClose are the window-control buttons
    // (h.WindowControl): events over them reach the app as usual.
    HitMinimize = platform.HitMinimize
    HitMaximize = platform.HitMaximize
    HitClose    = platform.HitClose
)
const DefaultTimeout = 5 * time.Second

DefaultTimeout is how long actions and expectations wait for their condition when no Timeout is given.

const EnvCheckDamage = "TELA_CHECK_DAMAGE"

EnvCheckDamage is the environment variable that turns CheckDamage on for every app ("1").

Functions

func AriaSnapshot

func AriaSnapshot(root tela.Component, opts ...Option) (yaml string, err error)

AriaSnapshot mounts root and returns its semantics tree as a Playwright aria snapshot, without a trailing newline: the YAML tela inspect --aria prints.

Example

yaml, err := telatest.AriaSnapshot(tela.C(Counter))
if err != nil {
    panic(err)
}
fmt.Println(yaml)
// Output:
// - 'heading "Count: 0" [level=1]'
// - button "Increment"

func LayoutJSON

func LayoutJSON(root tela.Component, opts ...Option) (js []byte, err error)

LayoutJSON mounts root and returns its layout tree as indented JSON, one object per box with its border box, edges, text and computed style (paragraphs also with the fragments of their inline elements; atomic inlines with inlineBox), ending with a newline: the output of tela inspect --layout. rect is the box as layout placed it; a box that is painted elsewhere, because of a transform or scrolled content, also has bounds, the same shape: the bounding box of its transformed border box in window coordinates, as tela.ElementRef.Bounds returns it.

func Render

func Render(root tela.Component, opts ...Option) (img *image.RGBA, err error)

Render mounts root, lays it out and paints it at the configured size and scale (default 800x600 at scale 1) and returns the image, in physical pixels over the root's background (white by default). Effects run as in a window and then clean up; state passive effects set is rendered only when the tree needs further frames. root is a node or a component, as for Mount; a Prop is an error.

Example

img, err := telatest.Render(Div(Class("size-8 bg-black")),
    telatest.Size(16, 16), telatest.Scale(2))
if err != nil {
    panic(err)
}
fmt.Println(img.Bounds().Dx(), img.Bounds().Dy(), img.RGBAAt(2, 2))
// Output: 32 32 {0 0 0 255}

func TempDesktopDirs

func TempDesktopDirs(tb testing.TB) string

TempDesktopDirs points package desktop's folders (ConfigDir, the settings store, saved window placements...) at a new temporary directory for the rest of the test, through the desktop.DirEnv variable, and returns it. Call it before the app reads a setting. Like t.Setenv, it cannot be used in parallel tests.

Types

type App

type App struct {
    // contains filtered or unexported fields
}

App is a UI mounted in a headless window: Tela's real pipeline and event dispatch with a window that is a record in memory and a clock that only moves when the app waits or the test calls App.Advance.

Actions and expectations settle the app first: they apply the pending state updates and run frames, advancing the clock to each frame, until nothing is dirty and no animation or scroll fling is running. A held pointer's long-press timer, and the fade of overlay scrollbars, fire only when the clock reaches them, with App.Advance or while an expectation waits.

An App must be used from the goroutine of its test; the state setters and tela.Post may be called from any goroutine.

func Mount

func Mount(tb testing.TB, root tela.Component, opts ...Option) *App

Mount shows root in a headless window and returns the running app. The window starts focused, so key events reach the focused element; it is unmounted when the test ends, running the cleanups of its effects. root is a node or a component: a struct component value, or a function component through its public wrapper, a function that returns its tela.C or tela.CP node (or use MountFunc).

app := telatest.Mount(t, Counter(), telatest.Size(400, 300))
app := telatest.Mount(t, Card{Title: "Inbox"})

Mount makes the app receive the functions given to tela.Post, which are delivered to one tree per process: do not mount apps that use Post from parallel tests.

The windows a tela.Window opens are opened in the headless backend too, by a fake window manager. The returned App is scoped to the main window; App.Windows, App.Window and App.WaitForWindow give an App scoped to each of the others, with the same queries, actions, snapshots and goldens (see "Windows" in the package documentation).

Example

app := telatest.Mount(t, tela.C(Counter), telatest.Size(400, 300))

app.Click(telatest.ByRole("button", "Increment"))
app.ExpectText("Count: 1")

fmt.Println(app.AriaSnapshot())
// Output:
// - 'heading "Count: 1" [level=1]'
// - button "Increment"

func MountApp

func MountApp(tb testing.TB, app tela.App, opts ...Option) *App

MountApp shows app.Root in a headless window configured like the app, the way tela.Main shows it in a real one, and returns the running app. The size, root class, color scheme, theme, class aliases, scrollbar and scroll settings and Dev of the app apply (see FromApp), and so do the fields of the OS window: Title, AppID, the size limits, FixedSize, Frameless, AlwaysOnTop, Hidden, Icon, Placement and OnClose (see Title, MinSize, Frameless, StartHidden, Placement and OnClose, the options that set them by hand). A Hidden app starts with its window hidden, and without an OnClose the app quits when its main window is asked to close (App.RequestClose, App.ExpectQuit). The options override all of them. It fails the test when app.Root is nil.

var App = tela.App{Title: "Notes", Width: 900, Height: 600, Root: Notes()}

func main() { tela.Main(App) }

func TestNotes(t *testing.T) {
    app := telatest.MountApp(t, App) // the same window as main's
    app.ExpectText("Notes")
}

tela snapshot and tela inspect read the same package-level var App. See Mount for what the app does and when it ends.

Example

// The App that main passes to tela.Main, and that `tela snapshot` reads
// from the package.
app := telatest.MountApp(t, tela.App{
    Title:     "Counter",
    Width:     480,
    Height:    320,
    RootClass: "bg-slate-100",
    Root:      tela.C(Counter),
})

app.Click(telatest.ByRole("button", "Increment"))
app.ExpectText("Count: 1")
fmt.Println(app.Screenshot().Bounds().Size()) // the App's window
// Output: (480,320)

func MountFunc

func MountFunc(tb testing.TB, fn func() tela.Node, opts ...Option) *App

MountFunc shows the component fn in a headless window and returns the running app: it is Mount(tb, tela.C(fn), opts...), for the common case of a test that mounts one component.

app := telatest.MountFunc(t, Counter, telatest.Size(400, 300))

See Mount for what the app does and when it ends.

Example

app := telatest.MountFunc(t, Counter, telatest.Size(400, 300))

app.Click(telatest.ByRole("button", "Increment"))
app.ExpectText("Count: 1")
fmt.Println(app.Count(telatest.ByRole("button", "Increment")))
// Output: 1

func (*App) ActiveDrag

func (a *App) ActiveDrag() (DragInfo, bool)

ActiveDrag returns the drag in progress: the one App.StartDrag began from an h.Draggable element, or another application's (Carrying), until a drop, a cancel or App.DropOutside.

app.StartDrag(telatest.ByTestID("card-7"))
info, _ := app.ActiveDrag()
if got := info.Data.GetData("application/x-card"); got != "7" { ... }

func (*App) Advance

func (a *App) Advance(d time.Duration)

Advance runs the app's clock forward by d: the frames, animations, scroll animations and flings, long-press timers and other deadlines that fall within d happen in order, each frame at its own time. It does not settle the app afterwards: work requested at the very end is handled by the next action, expectation or Advance.

Example

// The clock is manual: a long press fires when the app's time gets
// to 500 ms, whatever the machine's speed.
pressed := func() tela.Node {
    long, setLong := tela.UseState(false)
    return Button(OnLongPress(func(tela.PointerEvent) { setLong.Set(true) }),
        IfElse(long, Text("Held"), Text("Press and hold")))
}
app := telatest.Mount(t, tela.C(pressed))

app.Hover(telatest.ByRole("button", ""))
app.MouseDown()
app.Advance(600 * time.Millisecond)
app.MouseUp()
app.ExpectText("Held")
fmt.Println(app.AriaSnapshot())
// Output: - button "Held"

func (*App) AnswerFileDialog

func (a *App) AnswerFileDialog(paths ...string)

AnswerFileDialog answers the oldest open file dialog as the user would: with the given paths, or, without paths, by canceling it. The app's callback runs and the app settles. It fails the test when no file dialog is open, or when a dialog that takes one path gets several.

app.Click(telatest.ByRole("button", "Open…"))
app.AnswerFileDialog("/home/ana/notes/groceries.md")
app.ExpectText("groceries.md")

func (*App) AnswerMessage

func (a *App) AnswerMessage(answer desktop.Answer)

AnswerMessage answers the oldest open message box with the button answer, as the user would (a drawn box gets a real click on its button); the app's callback runs and the app settles. It fails the test when no box is open or the box has no such button.

app.Click(telatest.ByRole("button", "Close"))
app.AnswerMessage(desktop.AnswerNo) // "Save the changes?" — No

func (*App) AriaGolden

func (a *App) AriaGolden(name string)

AriaGolden compares the app's aria snapshot, with a final newline (what tela inspect prints), with the text file testdata/<name> (TELA_UPDATE=1 writes it). Use it for snapshots too long to write in the test; App.ExpectAria is better for short ones.

func (*App) AriaSnapshot

func (a *App) AriaSnapshot() string

AriaSnapshot returns the app's semantics tree as a Playwright aria snapshot, the YAML that tela inspect prints, without a trailing newline, after settling the app (under NoSettle, handling only the work due: running animations stay where they are). For a counter:

snap := app.AriaSnapshot()
// - heading "Count: 1" [level=1]
// - button "Increment"

func (*App) Backdrop

func (a *App) Backdrop() tela.Backdrop

Backdrop returns the system backdrop the window has asked the OS for (tela.App.Backdrop, tela.Window.Backdrop).

func (*App) Blocked

func (a *App) Blocked() bool

Blocked reports whether a visible modal window blocks this one; see App.BlockedBy.

func (*App) BlockedBy

func (a *App) BlockedBy() *App

BlockedBy returns the visible modal window that blocks this one, or nil. A blocked window takes no input: core drops it (after cancelling the pointers) and a press in it activates the modal window, and a request to close it is refused.

func (*App) Blocks

func (a *App) Blocks() *App

Blocks returns the window a modal window blocks while it is visible: its parent. It is nil for a window that is not modal, or hidden.

func (*App) Bounds

func (a *App) Bounds(l Locator) tela.Rect

Bounds returns the border box of the element l locates, in logical pixels from the window's top-left corner, after transforms and scroll offsets: the same rectangle as tela.ElementRef.Bounds. For an inline element it bounds its text.

r := app.Bounds(telatest.ByRole("button", "Save"))
if r.Width < 80 { t.Errorf("the button is %gpx wide", r.Width) }

func (*App) BringToFront

func (a *App) BringToFront()

BringToFront activates the window as the user does by clicking its task bar entry: it comes back from minimized and, when it is visible, takes the keyboard focus from the other windows. The pointer and key actions do it by themselves, as clicking or typing in a window does. Playwright's page.bringToFront().

func (*App) CancelDrag

func (a *App) CancelDrag()

CancelDrag cancels the drag in progress, as the user pressing Escape does: OnDragLeave runs on the window it is over, and the source's OnDragEnd runs with DropNone.

func (*App) CaptionDrags

func (a *App) CaptionDrags() int

CaptionDrags returns how many presses in a caption region started a move of the window by the fake window manager: a primary press that is not the second of a double click. A press in a caption region is the OS's: the app does not see it or the rest of its pointer sequence. Drive one with App.Click on an element inside an h.WindowDrag region, or App.ClickAt.

func (*App) ChooseMenuItem

func (a *App) ChooseMenuItem(path ...string)

ChooseMenuItem chooses an item of the window's native menu bar, named by the labels from the top-level entry down, as the user does with the mouse or Alt: its OnSelect runs and the app settles. It fails the test when the window has no menu bar, or the item is missing, disabled or opens a submenu.

app.ChooseMenuItem("File", "New")

func (*App) ChoosePopupItem

func (a *App) ChoosePopupItem(path ...string)

ChoosePopupItem chooses an item of the open native context menu (the oldest, if several), named by its label, and for an item in a submenu by the labels of the submenus first, as ClickTrayItem does: its OnSelect runs, then the menu's done, and the app settles. It fails the test when no menu is open, or the item is missing, disabled, or opens a submenu.

app.RightClick(telatest.ByText("groceries.md"))
app.ChoosePopupItem("Rename…")

func (*App) ClearAccentColor

func (a *App) ClearAccentColor()

ClearAccentColor makes the system report no accent color, as a desktop without one does.

func (*App) Click

func (a *App) Click(l Locator, opts ...ClickOption)

Click moves the pointer to the center of the element l locates, presses the primary button and releases it: real pointer events (hover, :active, focus on press, the gesture arena and the click), not a call to the handler. Each Click is a first click (ClickCount 1) even right after another, as in Playwright; use App.DoubleClick for two. It waits for the element to be visible, enabled, in view and not covered by another element, and fails the test when l matches none or several. With Right it presses the secondary button instead (the element's OnContextMenu runs, not its OnClick); App.Tap and App.LongPress are the touch counterparts.

func (*App) ClickAt

func (a *App) ClickAt(x, y float32, opts ...ClickOption)

ClickAt clicks at the point (x, y) of the window, in logical pixels from its top-left corner, with the same real pointer events as App.Click (hover, :active, focus on press, the gesture arena and the click) but without looking for an element: whatever is under the point receives them, like a user clicking there. It is for what a locator cannot name (a canvas, a chart, a drop zone found with App.Bounds) and fails the test when the point is outside the window. Right clicks with the secondary button, to open a context menu at a point.

r := app.Bounds(telatest.ByTestID("chart"))
app.ClickAt(r.X+r.Width/2, r.Y+10)
app.ClickAt(r.X+20, r.Y+20, telatest.Right())

Example

app := telatest.MountFunc(t, Counter, telatest.Size(400, 300))

r := app.Bounds(telatest.ByRole("button", "Increment"))
app.ClickAt(r.X+r.Width/2, r.Y+r.Height/2)
app.ExpectText("Count: 1")
fmt.Println("clicked")
// Output: clicked

func (*App) ClickNotification

func (a *App) ClickNotification(title string)

ClickNotification clicks the open notification with the given title (the newest, if several): its OnClick runs and it closes.

func (*App) ClickNotificationAction

func (a *App) ClickNotificationAction(title, action string)

ClickNotificationAction clicks the button labeled action of the open notification with the given title: the action's OnClick runs and the notification closes.

func (*App) ClickTray

func (a *App) ClickTray()

ClickTray clicks the app's tray icon (its OnClick runs). It fails the test unless the app shows exactly one.

func (*App) ClickTrayItem

func (a *App) ClickTrayItem(path ...string)

ClickTrayItem chooses an item of the tray icon's menu (its OnSelect runs), named by its label, and for an item in a submenu by the labels of the submenus first:

app.ClickTrayItem("Quit")
app.ClickTrayItem("Theme", "Dark")

It fails the test when the app does not show exactly one tray icon, or the item is missing, disabled, or opens a submenu.

func (*App) Clipboard

func (a *App) Clipboard() *tela.DataTransfer

Clipboard returns a copy of the clipboard's content, after settling the app: what the app wrote (desktop.WriteClipboard, a text field's copy), or what SetClipboard put there.

app.Click(telatest.ByRole("button", "Copy link"))
if got := app.Clipboard().Text(); got != "notes:open?id=3" {
    t.Errorf("clipboard = %q", got)
}

func (*App) Close

func (a *App) Close()

Close unmounts the whole app now (every window, not only the one this App is scoped to), running the cleanups of its effects. The test does it when it ends; call it to test unmounting. Using the app afterwards fails the test. To test what the user closing a window does, use App.RequestClose.

func (*App) Closed

func (a *App) Closed() bool

Closed reports whether the window is closed: it unmounted (its tela.Window left the tree, or the user's close request was granted) or the app ended.

func (*App) Count

func (a *App) Count(l Locator) int

Count returns how many elements l matches now, after settling the app (under NoSettle, handling only the work due: running animations stay where they are). It does not wait.

func (*App) Cursor

func (a *App) Cursor() string

Cursor returns the CSS name of the mouse cursor the window shows ("default", "pointer", "not-allowed"...), after settling the app (under NoSettle, handling only the work due).

func (*App) DismissNotification

func (a *App) DismissNotification(title string)

DismissNotification closes the open notification with the given title as the user does with its close button (or as it expires): its OnClose runs.

func (*App) DismissPopupMenu

func (a *App) DismissPopupMenu()

DismissPopupMenu closes the open native context menu without choosing (Escape, a click elsewhere): the menu's done runs with chosen false.

func (*App) DoubleClick

func (a *App) DoubleClick(l Locator)

DoubleClick clicks the element twice in a row, so the second click has a click count of 2 and OnDoubleClick runs. See App.Click.

func (*App) Drag

func (a *App) Drag(from, to Locator, opts ...DragOption)

Drag presses the primary button at the center of from, moves the pointer in steps to the center of to, and releases it there: a real drag through the gesture arena (OnDragStart, OnDrag, OnDragEnd), the pointer capture and the hover states of the elements on the way. The center of to is read once the drag has started.

func (*App) DragBy

func (a *App) DragBy(l Locator, dx, dy float32, opts ...DragOption)

DragBy drags the element l locates by (dx, dy) logical pixels from its center. See App.Drag.

func (*App) DragFromTo

func (a *App) DragFromTo(from, to tela.Point, opts ...DragOption)

DragFromTo presses the primary button at the point from, moves the pointer in steps to the point to and releases it there, in logical pixels from the window's top-left corner: the drag of App.Drag without locators, for sources and targets a locator cannot name (a canvas, a slider track at a given value). It fails the test when a point is outside the window.

r := app.Bounds(telatest.ByTestID("track"))
app.DragFromTo(tela.Point{X: r.X + 4, Y: r.Y + 8}, tela.Point{X: r.X + r.Width/2, Y: r.Y + 8})

Example

moved := 0
area := func() tela.Node {
    return Div(Class("size-32 bg-slate-200"),
        OnDrag(func(e tela.DragEvent) { moved = int(e.OffsetX) }))
}
app := telatest.MountFunc(t, area, telatest.Size(200, 200))

app.DragFromTo(tela.Point{X: 10, Y: 10}, tela.Point{X: 90, Y: 10})
fmt.Println(moved)
// Output: 80

func (*App) DragTo

func (a *App) DragTo(from, to Locator, opts ...DragOption) tela.DropEffect

DragTo drags the element from locates, which must be h.Draggable(true), to the element to locates in the same window and drops it there, like Playwright's locator.dragTo, through the app's drag and drop handlers (App.StartDrag, App.HoverDrag, App.DropOn). It returns the effect the target accepted, tela.DropNone when it refused; the source's OnDragEnd got the same one.

app.DragTo(telatest.ByTestID("card-7"), telatest.ByTestID("column-done"))
app.ExpectText("Card 7")

For the gesture drag of OnDrag (sliders, movable panels) use App.Drag.

func (*App) DragToWindow

func (a *App) DragToWindow(from Locator, window *App, to Locator, opts ...DragOption) tela.DropEffect

DragToWindow drags the element from locates to the element to locates in another window of the app, window (an App from App.Windows, App.Window or App.WaitForWindow), and drops it there (App.DragTo).

app.DragToWindow(telatest.ByTestID("tab-1"), app.Window("Detached"), telatest.ByTestID("tabs"))

func (*App) DropData

func (a *App) DropData(l Locator, payload DragPayload, opts ...DragOption) tela.DropEffect

DropData drops payload from another application on the element l locates, and returns the effect the app accepted (App.DropOn).

app.DropData(telatest.ByTestID("editor"), telatest.DragHTML("<b>Hi</b>").And(telatest.DragText("Hi")))

func (*App) DropFiles

func (a *App) DropFiles(l Locator, paths ...string) tela.DropEffect

DropFiles drops files from a file manager on the element l locates, and returns the effect the app accepted (App.DropOn). The paths must be absolute.

path := filepath.Join(t.TempDir(), "photo.png")
if app.DropFiles(telatest.ByTestID("upload"), path) == tela.DropNone { t.Fatal("refused") }
app.ExpectText("photo.png")

func (*App) DropOn

func (a *App) DropOn(l Locator, opts ...DragOption) tela.DropEffect

DropOn releases the drag in progress over the center of the element l locates, after moving it there as App.HoverDrag does, and returns the effect the app accepted: tela.DropNone when it did not take the drop, tela.DropCopy, DropMove or DropLink when it did. An app takes a drop when its latest OnDragOver on the way called PreventDefault with an effect the source allows (a drag with Carrying allows copy, move and link unless Allowed says otherwise), and OnDrop did not set DropNone. A refused drop runs OnDragLeave instead of OnDrop. The source's OnDragEnd runs with the same effect.

effect := app.DropOn(telatest.ByRole("region", "Inbox"), telatest.Carrying(telatest.DragText("hello")))
if effect != tela.DropCopy { t.Fatalf("the inbox did not copy the text: %v", effect) }

func (*App) DropOutside

func (a *App) DropOutside(effect tela.DropEffect)

DropOutside ends the drag the app started over another application, which performs effect: the source's OnDragEnd runs with it. Pass tela.DropNone for an application that refuses it. effect must be one the drag allows (its DataTransfer.EffectAllowed).

app.StartDrag(telatest.ByTestID("card-7"))
app.DropOutside(tela.DropMove)
app.ExpectNoText("Card 7") // OnDragEnd removed the moved card

func (*App) Expect

func (a *App) Expect(l Locator, states ...State)

Expect waits until the element l locates satisfies every state, like Playwright's toBeChecked, toBeDisabled and toBeExpanded: it settles the app and checks, and while a state does not hold lets a frame of app time pass and checks again, until it holds or the Timeout expires. The locator must match exactly one element (strictness). The failure message says which states differ, shows the element's actual states and its line of the aria snapshot.

app.Expect(telatest.ByRole("switch", "Wi-Fi"), telatest.Checked(true), telatest.Disabled(false))
app.Expect(telatest.ByRole("button", "Menu"), telatest.Expanded(true))
app.Expect(telatest.ByRole("textbox", "Email"), telatest.HasValue("ada@example.com"))

Example

app := telatest.MountFunc(t, Settings, telatest.Size(400, 400))

wifi := telatest.ByRole("switch", "Wi-Fi")
app.Expect(wifi, telatest.Checked(false))
app.Click(wifi)
app.Expect(wifi, telatest.Checked(true), telatest.Disabled(false))
app.Expect(telatest.ByRole("button", "Save"), telatest.Disabled(true))
fmt.Println("ok")
// Output: ok

func (*App) ExpectAria

func (a *App) ExpectAria(yaml string)

ExpectAria waits until the window's aria snapshot equals yaml (see App.AriaSnapshot), like Playwright's toMatchAriaSnapshot in exact mode: after trimming the surrounding blank lines and removing the indentation common to all lines, so a raw string can be indented like the code around it.

app.ExpectAria(`
    - heading "Count: 1" [level=1]
    - button "Increment"
`)

Example

app := telatest.Mount(t, tela.C(Counter))
app.Click(telatest.ByText("Increment"))

// The expected snapshot may be indented like the code around it.
app.ExpectAria(`
    - 'heading "Count: 1" [level=1]'
    - button "Increment"
`)
fmt.Println("matches")
// Output: matches

func (*App) ExpectClosed

func (a *App) ExpectClosed()

ExpectClosed waits until the window is closed (see App.Closed) and fails the test at the timeout. A close request the app refuses (a tela.Window whose OnClose does not update the state that renders it) leaves the window open.

func (*App) ExpectCount

func (a *App) ExpectCount(l Locator, n int)

ExpectCount waits until l matches exactly n elements.

func (*App) ExpectFocused

func (a *App) ExpectFocused(l Locator)

ExpectFocused waits until the element l locates has the keyboard focus.

func (*App) ExpectNoText

func (a *App) ExpectNoText(text string)

ExpectNoText waits until the window does not show text (see App.ExpectText).

func (*App) ExpectNoWindow

func (a *App) ExpectNoWindow(title string)

ExpectNoWindow waits until no open window has the title, and fails the test at the timeout.

func (*App) ExpectQuit

func (a *App) ExpectQuit()

ExpectQuit waits until the app quits (see App.HasQuit) and fails the test at the timeout. Once the app quit, its windows are unmounted and the cleanups of their effects have run, so the test can assert them.

app.Click(telatest.ByRole("button", "Quit"))   // OnClick(tela.Quit)
app.ExpectQuit()
if !cleaned { t.Error("the effect did not clean up") }

func (*App) ExpectRole

func (a *App) ExpectRole(role, name string)

ExpectRole waits until an element with the role and accessible name is visible (see ByRole; an empty name accepts any). It passes when one or more elements match: use App.ExpectCount to assert there is exactly one.

app.ExpectRole("heading", "Settings")

func (*App) ExpectRunning

func (a *App) ExpectRunning()

ExpectRunning fails the test when the app quit (see App.HasQuit): the check of an OnClose that hides the window to keep the app in the tray.

func (*App) ExpectText

func (a *App) ExpectText(text string)

ExpectText waits until the window shows text: it is contained in the window's text content (whitespace normalized, hidden elements left out, block boundaries counting as spaces; case sensitive).

app.ExpectText("Count: 1")

func (*App) ExpectTitle

func (a *App) ExpectTitle(title string)

ExpectTitle waits until the window's title is title.

func (*App) ExpectValue

func (a *App) ExpectValue(l Locator, value string)

ExpectValue waits until the text field l locates holds value (Playwright's toHaveValue).

app.Type("hello")
app.ExpectValue(telatest.ByRole("textbox", "Message"), "hello")

func (*App) ExpectWindowCount

func (a *App) ExpectWindowCount(n int)

ExpectWindowCount waits until the app has n open windows (the main window counts), and fails the test at the timeout.

func (*App) ExpectWindowState

func (a *App) ExpectWindowState(conds ...WindowCond)

ExpectWindowState waits until the window's state meets every condition, and fails the test at the timeout, showing the unmet conditions and the state it has.

win.Maximize()
win.ExpectWindowState(telatest.Maximized(true), telatest.Minimized(false))

func (*App) FailSecrets

func (a *App) FailSecrets(err error)

FailSecrets makes every secret call of the app fail with err from now on, until FailSecrets(nil): desktop.ErrSecretCanceled plays the user dismissing the keyring's unlock prompt. (A desktop without secure storage is App.Unsupported(telatest.Secrets).)

app.FailSecrets(desktop.ErrSecretCanceled)
app.Click(telatest.ByRole("button", "Unlock"))
app.ExpectText("The keyring stayed locked")

func (*App) FailShortcuts

func (a *App) FailShortcuts(err error)

FailShortcuts makes every global shortcut the app registers from now on fail with err, until FailShortcuts(nil); shortcuts registered already keep their state. An error wrapping desktop.ErrAppNotRegistered plays a Linux desktop without the app's entry (desktop.RegisterApp). OnStatus gets ShortcutFailed with an error wrapping err.

app.FailShortcuts(desktop.ErrAppNotRegistered)
app.Click(telatest.ByRole("switch", "Quick note")) // registers again

func (*App) FailTrash

func (a *App) FailTrash(err error)

FailTrash makes every desktop.MoveToTrash of the app fail with err from now on, until FailTrash(nil); the file stays in place: a file in use, a folder the user may not change. (A desktop without a trash, or a file it cannot take, is App.Unsupported(telatest.Trash).)

app.FailTrash(errors.New("the file is in use"))

func (*App) FileDialogs

func (a *App) FileDialogs() []FileDialog

FileDialogs returns the file dialogs open now, oldest first, after settling the app.

func (*App) Fill

func (a *App) Fill(l Locator, text string)

Fill fills the text field l locates (an h.Input or h.Textarea) with text, like Playwright's fill: after the actionability checks of the pointer actions, it focuses the field, selects its value and replaces it with text as a single insertText edit (an empty text deletes the value), firing OnBeforeInput and OnChange once. MaxLength applies. It fails the test when the element is not an editable text field.

app.Fill(telatest.ByRole("textbox", "Email"), "ana@example.com")

Use App.Type to send the characters one key at a time instead.

Example

form := func() tela.Node {
    name, setName := tela.UseState("")
    return Div(
        Label(For("name"), Text("Name")),
        Input(ID("name"), Bind(name, setName)),
        P(Textf("Hello, %s!", name)),
    )
}
app := telatest.Mount(t, tela.C(form))

app.Fill(telatest.ByRole("textbox", "Name"), "Ada")
app.ExpectValue(telatest.ByRole("textbox", "Name"), "Ada")
app.ExpectText("Hello, Ada!")
fmt.Println(app.Count(telatest.ByText("Hello, Ada!")))
// Output: 1

func (*App) Focus

func (a *App) Focus(l Locator)

Focus gives the keyboard focus to the element l locates, like calling focus() on it: OnBlur and OnFocus run. It fails the test when the element cannot take the focus (give it a Button, TabIndex or an interactive role element).

func (*App) GlobalShortcuts

func (a *App) GlobalShortcuts() []GlobalShortcut

GlobalShortcuts returns the global shortcuts the app has registered, in registration order, after settling the app.

func (*App) Golden

func (a *App) Golden(name string, opts ...GoldenOption)

Golden compares the window's content, after settling the app (with NoSettle, at the current time: see App.Screenshot), with the PNG testdata/<name> next to the test (a name without the ".png" extension gets it), bit by bit unless a Tolerance is given. TELA_UPDATE=1 writes the golden instead of comparing (ADR-022); review the PNG before committing it. On a mismatch, <name>_actual.png and <name>_diff.png are written next to the golden.

The picture is physical pixels, so a golden depends on Size and Scale; it also shows the overlay scrollbars while they are visible: advance the clock to hide them first when that matters.

func (*App) GoldenScales

func (a *App) GoldenScales(name string, scales ...float64)

GoldenScales compares the window with one golden per display scale: testdata/<name>_x<scale>.png, with the scale written like %g (name_x1.png, name_x2.png, name_x1.5.png), the naming of the examples' goldens. A name with the ".png" extension has it removed first. For each scale the window is moved to a display with that scale, as when a user drags it between monitors (the logical size is kept, the picture is size*scale pixels), settled like App.Golden does, and compared; the app keeps its state and its original scale at the end. The comparison is bit by bit and TELA_UPDATE=1 writes the goldens, as with App.Golden.

app.GoldenScales("settings", 1, 2) // testdata/settings_x1.png, settings_x2.png

Example

GoldenScales has no output to check: it compares files in testdata.

app := telatest.MountFunc(t, Counter, telatest.Size(400, 300))

// testdata/counter_x1.png and testdata/counter_x2.png; TELA_UPDATE=1
// writes them.
app.GoldenScales("counter", 1, 2)

func (*App) HasQuit

func (a *App) HasQuit() bool

HasQuit reports whether the app quit: tela.Quit ran, or the main window was asked to close without a tela.App.OnClose. A quit app is over, as when tela.Main returns: every window was unmounted, so the cleanups of the effects ran, and using the app fails the test. The app is settled first.

func (*App) HitAt

func (a *App) HitAt(x, y float32) HitKind

HitAt returns what the OS would find at the point (x, y), in logical pixels from the window's top-left corner: the kind of the last hit region that contains it, or HitClient.

func (*App) HitRegions

func (a *App) HitRegions() []HitRegion

HitRegions returns the hit regions the window sent to the OS, in order (later regions are on top): the drag areas, the holes in them and the window-control buttons that h.WindowDrag, h.WindowNoDrag and h.WindowControl mark. It is nil for a window without them.

func (*App) HoldAppEntry

func (a *App) HoldAppEntry(id, path string)

HoldAppEntry plays another program's desktop entry of the app ID id (a package's), at path: desktop.RegisterApp then writes nothing unless it passes desktop.Replace, and desktop.AppStatus reports it. "" removes it.

func (*App) HoldAssociation

func (a *App) HoldAssociation(as desktop.Association, other string)

HoldAssociation plays another application, named other, holding a: the app's desktop.Register of it fails with desktop.ErrAssociationTaken unless it passes desktop.Replace. "" releases it.

func (*App) Hover

func (a *App) Hover(l Locator)

Hover moves the pointer to the center of the element l locates, so its hover: and group-hover: states apply. It waits for the element to be visible, in view and not covered.

func (*App) HoverDrag

func (a *App) HoverDrag(l Locator, opts ...DragOption)

HoverDrag moves the drag in progress over the center of the element l locates and leaves it there, so the app can show its drag-over state: OnDragEnter on the element, OnDragLeave on the one it left, OnDragOver. The pointer moves in steps (default 12; Steps) when the drag is already over this window, and enters it directly otherwise.

With Carrying it starts another application's drag first, over no window. The window need not be the drag's source: calling HoverDrag on the App of another window of the app moves a drag between windows.

app.HoverDrag(telatest.ByTestID("dropzone"), telatest.Carrying(telatest.DragFiles(path)))
app.ExpectText("Drop to upload")
app.LeaveDrag()
app.ExpectNoText("Drop to upload")

func (*App) InjectLaunch

func (a *App) InjectLaunch(l desktop.Launch)

InjectLaunch plays the user starting the app again while it runs, as a single-instance app sees it (desktop.SingleInstance): the new process forwards l (its Args and Dir; Forwarded is ignored) and exits, the main window comes to the front (shown if hidden), and every desktop.UseLaunch handler gets the launch. The app settles.

app := telatest.MountApp(t, desktop.SingleInstance(App))
app.InjectLaunch(desktop.Launch{Args: []string{"notes:open?id=3"}})
app.ExpectText("Groceries")

func (*App) LastWindow

func (a *App) LastWindow() *App

LastWindow returns the newest open window: the one opened last, or the main window when it is the only one.

func (*App) LayoutJSON

func (a *App) LayoutJSON() []byte

LayoutJSON returns the layout tree of the running app as indented JSON, ending with a newline, after settling it (under NoSettle, handling only the work due): the output of tela inspect --layout and of the function LayoutJSON, for the app as it is now, with the state its actions built up.

func (*App) LayoutJSONOf

func (a *App) LayoutJSONOf(l Locator) []byte

LayoutJSONOf returns the layout subtree of the element l locates, in the same JSON as App.LayoutJSON (indented, ending with a newline, rects in window coordinates before scroll offsets and transforms, with the painted "bounds" where they differ): the box of the element with everything inside it, as tela inspect --layout shows it at that place. An inline element, a text or a display: contents element gives the box of its nearest ancestor that has one. Like the other locator queries it settles the app first (under NoSettle, handling only the work due), and fails the test when l matches no element or several. tela mcp's inspect tool is built on it (ADR-194).

js := app.LayoutJSONOf(telatest.ByRole("button", "Save"))

func (*App) LeaveDrag

func (a *App) LeaveDrag()

LeaveDrag moves the drag in progress out of this window, as the pointer leaving it does: OnDragLeave runs. The drag goes on.

func (*App) LongPress

func (a *App) LongPress(l Locator)

LongPress presses the element l locates with a finger at its center, holds it past the long-press threshold (500 ms of app time: the clock moves, so the timers run) and lifts it. The element's OnLongPress runs while the finger is down and the press then ends without a click. A touch long press also opens the menu of a kit.ContextMenu or aria.UseContextMenuTrigger (a long press of the mouse does not; see App.Click with Delay). It waits for the element to be visible, enabled, in view and not covered.

app.LongPress(telatest.ByTestID("photo"))
app.ExpectCount(telatest.ByRole("menu", "Actions"), 1)

func (*App) MainWindow

func (a *App) MainWindow() *App

MainWindow returns the App scoped to the app's main window, the one Mount returned.

func (*App) Maximize

func (a *App) Maximize()

Maximize maximizes the window to the fake screen (Screen) as the user does with the OS; it also brings it back from minimized.

func (*App) Measure

func (a *App) Measure(f func()) Stats

Measure runs f, which performs actions on the app, settles the app afterwards like an action does (with NoSettle, only the work due is handled) and returns the work the tree did meanwhile: the difference of two readings of App.Stats, so that a test can assert how narrow an update was without keeping the readings itself. The app is brought to rest before f runs, so work pending from earlier steps is not counted.

st := app.Measure(func() { app.Click(telatest.ByRole("button", "Increment")) })
if st.Builds != 1 { t.Errorf("the click rebuilt %d components, want 1", st.Builds) }

Example

app := telatest.MountFunc(t, Counter, telatest.Size(400, 300))

st := app.Measure(func() {
    app.Click(telatest.ByRole("button", "Increment"))
})
fmt.Println("builds:", st.Builds, "frames:", st.Frames > 0)
// Output: builds: 1 frames: true

func (*App) MenuBar

func (a *App) MenuBar() []MenuItem

MenuBar returns the native menu bar of this App's window (a mounted desktop.MenuBar), nil without one, after settling the app.

func (*App) MessageBoxes

func (a *App) MessageBoxes() []MessageBox

MessageBoxes returns the message boxes open now, oldest first (native ones before drawn ones), after settling the app.

func (*App) Minimize

func (a *App) Minimize()

Minimize minimizes the window, as the user does with the OS: it loses the focus and gets no frames until it is restored or activated.

func (*App) Modal

func (a *App) Modal() bool

Modal reports whether the window is modal to the window it was declared in (tela.Window.Modal).

func (*App) MouseDown

func (a *App) MouseDown()

MouseDown presses the primary button at the pointer's position (move it first with MouseMove, Hover or Click).

func (*App) MouseMove

func (a *App) MouseMove(x, y float32)

MouseMove moves the pointer to (x, y), in logical pixels from the window's top-left corner.

func (*App) MouseUp

func (a *App) MouseUp()

MouseUp releases the primary button.

func (*App) Move

func (a *App) Move(x, y int)

Move moves the window so that its content area's top-left corner is at (x, y) physical pixels of the fake desktop, as the user dragging it. The position shows in App.Placement from then on.

func (*App) Notifications

func (a *App) Notifications() []Notification

Notifications returns the notifications the app showed, oldest first, closed ones included, after settling the app.

func (*App) Now

func (a *App) Now() time.Duration

Now returns the app's clock: the time since the window was created, as moved by waits and App.Advance.

func (*App) Parent

func (a *App) Parent() *App

Parent returns the window this one was declared in (the App of the window whose component rendered the tela.Window), or nil for the main window.

func (*App) PixelAt

func (a *App) PixelAt(x, y float32) color.RGBA

PixelAt returns the color of the window's current frame at the point (x, y), in logical pixels from the top-left corner. The frame is in device pixels, so the sampled one is the device pixel that contains the point: (floor(x*scale), floor(y*scale)), where scale is the window's display scale; at scale 2, PixelAt(10, 10) reads the pixel (20, 20), the top-left one of the four that cover the logical pixel. The app is settled first (with NoSettle, only the work due is handled). The color is as stored in the frame (alpha-premultiplied; windows are opaque, so A is 255). It fails the test when the point is outside the window.

if c := app.PixelAt(40, 20); c != (color.RGBA{R: 59, G: 130, B: 246, A: 255}) {
    t.Errorf("the button is %v, want blue-500", c)
}

Example

swatch := func() tela.Node {
    return Div(Class("size-4 bg-[#ff0000]"))
}
// At scale 2 the logical point (8, 8) is the device pixel (16, 16).
app := telatest.MountFunc(t, swatch, telatest.Size(32, 32), telatest.Scale(2))

fmt.Println(app.PixelAt(8, 8), app.PixelAt(20, 20))
// Output: {255 0 0 255} {255 255 255 255}

func (*App) Placement

func (a *App) Placement() tela.WindowPlacement

Placement returns where the window is (tela.WindowHandle.Placement): its size outside maximized and fullscreen, its position once the app or App.Move set one, the state to open in. It still answers after the window closed.

func (*App) PopupMenus

func (a *App) PopupMenus() []PopupMenu

PopupMenus returns the native context menus open in this App's window, oldest first, after settling the app.

func (*App) Press

func (a *App) Press(combo string)

Press presses and releases a key, sending the events to the focused element (or, when nothing is focused, to the outermost element of the content). The syntax is Playwright's: a key name or character, with modifiers joined by "+":

app.Press("Enter")
app.Press("Shift+Tab")
app.Press("Control+s")
app.Press("ControlOrMeta+s") // Meta+s under the Mac flavor, Control+s elsewhere
app.Press("ArrowDown")
app.Press("a")              // the character "a"

Each key is a press and a release with the physical Code, the logical Key, the Text it produces (none for Enter, arrows...), its Location and the modifier state, in the US layout; modifiers are pressed first, as left keys, and released last.

Example

search := func() tela.Node {
    query, setQuery := tela.UseState("")
    submitted, setSubmitted := tela.UseState("")
    return Form(OnSubmit(func(tela.SubmitEvent) { setSubmitted.Set(query) }),
        Input(AriaLabel("Search"), Bind(query, setQuery)),
        P(Textf("Searched: %q", submitted)),
    )
}
app := telatest.Mount(t, tela.C(search))

app.Focus(telatest.ByRole("textbox", "Search"))
app.Type("tela")   // one key at a time
app.Press("Enter") // submits the form
app.ExpectText(`Searched: "tela"`)
fmt.Println("ok")
// Output: ok

func (*App) PressShortcut

func (a *App) PressShortcut(id string)

PressShortcut presses the global shortcut id as the user does, from any application: its OnPress runs and the app settles. It fails the test when no mounted desktop.GlobalShortcut has the ID or it is not bound.

app.PressShortcut("new-note")
app.ExpectText("Untitled note")

func (*App) RegisteredApps

func (a *App) RegisteredApps() []desktop.HandlerApp

RegisteredApps returns the apps registered with desktop.RegisterApp and not unregistered, sorted by ID, after settling the app.

desktop.RegisterApp(desktop.ForApp(desktop.HandlerApp{Exec: "/opt/notes/notes"}))
apps := app.RegisteredApps() // [{com.example.Notes Notes /opt/notes/notes …}]

func (*App) Registrations

func (a *App) Registrations() []Registration

Registrations returns the schemes and file types registered by the app (and by others the test played), sorted by association, after settling the app.

app.Click(telatest.ByRole("button", "Open notes: links with Notes"))
regs := app.Registrations() // [{notes: {com.example.Notes Notes /path/notes …} true}]

func (*App) ReleaseShortcut

func (a *App) ReleaseShortcut(id string)

ReleaseShortcut releases the global shortcut id (its OnRelease runs), as Linux reports; Windows reports no releases.

func (*App) RequestClose

func (a *App) RequestClose()

RequestClose asks to close the window as the user does with the close button of its title bar or Alt+F4: the window's OnClose runs (for the main window tela.App.OnClose, or the app quits). Whether the window closes is up to the app: use App.ExpectClosed, App.ExpectQuit or App.ExpectWindowCount to assert it. A blocked window refuses the request and the modal window above it is activated. To press the close button of a custom title bar, click it (App.Click).

func (*App) Resize

func (a *App) Resize(width, height float32)

Resize changes the logical size of the window, in pixels, as a user resizing it: the app keeps its state, the breakpoint variants (md:, max-lg:) and the layout follow the new size, and the app is settled afterwards (with NoSettle, only the work due is handled). The display scale is kept; App.SetScale changes it. It fails the test for a size that is not positive. tela mcp's resize tool is built on it (ADR-174).

app.Resize(390, 844) // a phone-sized window
app.ExpectCount(telatest.ByRole("button", "Open menu"), 1)

func (*App) Restore

func (a *App) Restore()

Restore brings the window back to its normal state: not minimized, not maximized and not fullscreen.

func (*App) Screenshot

func (a *App) Screenshot() *image.RGBA

Screenshot returns a copy of the window's content, in physical pixels (the logical size times the scale), after settling the app; with NoSettle, the content at the current time, running animations where they are.

func (*App) Scroll

func (a *App) Scroll(l Locator, dx, dy float32)

Scroll moves the pointer to the center of the element l locates and scrolls the wheel there by (dx, dy) logical pixels, positive to the right and down, as a touchpad does: the nearest scroll container under the pointer that can move scrolls at once, with no animation. Use Press("PageDown") or Press("End") for the animated key scrolling.

func (*App) SetAccentColor

func (a *App) SetAccentColor(color tela.Color)

SetAccentColor changes the system's accent color (tela.UseAccentColor returns it). Use App.ClearAccentColor for a system without one.

func (*App) SetClipboard

func (a *App) SetClipboard(dt *tela.DataTransfer)

SetClipboard puts dt's content on the clipboard, as another application copying it would (nil empties it), for the app to paste.

var dt tela.DataTransfer
dt.SetFiles("/home/ana/photo.png")
app.SetClipboard(&dt)
app.Press("Control+V")

func (*App) SetDarkMode

func (a *App) SetDarkMode(dark bool)

SetDarkMode changes whether the system prefers the dark color scheme: the dark: variants apply and tela.UseColorScheme reports it, unless the app sets its own scheme (tela.App.ColorScheme, tela.SetColorScheme, ColorScheme).

func (*App) SetDefaultHandler

func (a *App) SetDefaultHandler(as desktop.Association, holder string)

SetDefaultHandler plays the user (or another application) making the application holder the default handler of as; "" removes the explicit default.

func (*App) SetFullscreen

func (a *App) SetFullscreen(on bool)

SetFullscreen makes the window fullscreen on the fake screen, or leaves fullscreen; entering also brings it back from minimized.

func (*App) SetHighContrast

func (a *App) SetHighContrast(high bool)

SetHighContrast changes whether the system asks for high contrast: the contrast-more: variants apply and tela.UseHighContrast reports it.

func (*App) SetReducedMotion

func (a *App) SetReducedMotion(reduced bool)

SetReducedMotion changes whether the system asks for reduced motion: the motion-reduce: variants apply, and transitions that start from now on take no time (tela.UseReducedMotion).

func (*App) SetScale

func (a *App) SetScale(scale float64)

SetScale moves the window to a display with another scale (device pixels per logical pixel), as a user dragging it between monitors: the logical size and the state are kept and screenshots become width*scale x height*scale pixels. The app is settled afterwards (with NoSettle, only the work due is handled). It fails the test for a scale that is not positive. App.GoldenScales compares goldens at several scales without keeping the change.

app.SetScale(2)

func (*App) SetShortcutStatus

func (a *App) SetShortcutStatus(id string, status desktop.ShortcutStatus, trigger string)

SetShortcutStatus plays the OS changing the state of the registered shortcut id: another application took its keys (ShortcutTaken), the user declined it (ShortcutRefused) or removed it (ShortcutRevoked), or bound it to other keys (ShortcutBound with trigger). Its OnStatus runs and the app settles. trigger is ignored unless the status is ShortcutBound; "" then means the keys asked for.

app.SetShortcutStatus("new-note", desktop.ShortcutTaken, "")
app.ExpectText("Ctrl+Alt+N is taken by another application")

func (*App) SetShortcutUnassigned

func (a *App) SetShortcutUnassigned(id string)

SetShortcutUnassigned plays a desktop that registers the shortcut id without assigning it keys, as Hyprland does (the user binds it in hyprland.conf): its OnStatus gets ShortcutBound with an empty Trigger, and the app settles. PressShortcut still works, as once the user bound it.

app.SetShortcutUnassigned("new-note")
app.ExpectText("bind = CTRL ALT, N, global, com.example.notes:new-note")

func (*App) SetStoredSecret

func (a *App) SetStoredSecret(name string, secret []byte)

SetStoredSecret stores a secret under name, as an earlier run of the app would have, before the app reads it with desktop.Secret.

func (*App) SetTextScale

func (a *App) SetTextScale(scale float64)

SetTextScale changes the system's text scale: 1rem becomes the theme's rem times scale, so type, rem-based spacing and breakpoints change (tela.UseTextScale), unless the app ignores it (tela.App.IgnoreTextScale). scale is above 0 and at most 4; 1 is the default.

func (*App) StartDrag

func (a *App) StartDrag(l Locator) DragInfo

StartDrag presses the primary button at the center of the element l locates and moves the pointer away until the app starts a drag, then leaves the drag in progress, owned by the "OS" as a real drag is: the app gets no more pointer events until it ends. Continue it with App.HoverDrag, App.DropOn, App.DropOutside, App.LeaveDrag or App.CancelDrag. It returns what the drag carries, as App.ActiveDrag does.

The element must be h.Draggable(true) and its OnDragStart must put data in e.DataTransfer; an element without data stays an ordinary drag gesture, and StartDrag fails the test.

app.StartDrag(telatest.ByTestID("card-7"))
app.HoverDrag(telatest.ByTestID("column-done"))
app.ExpectText("Release to move here") // the drag-over state of the column
app.DropOn(telatest.ByTestID("column-done"))

func (*App) Stats

func (a *App) Stats() Stats

Stats returns the work counters of the app's tree, after settling it (under NoSettle, handling only the work due).

func (*App) StoredSecret

func (a *App) StoredSecret(name string) ([]byte, bool)

StoredSecret returns the secret the app stored under name (desktop.SetSecret), and whether there is one. It does not fail while FailSecrets is in effect.

func (*App) Tap

func (a *App) Tap(l Locator)

Tap taps the element l locates with a finger: it puts a touch pointer down at the element's center and lifts it, with real touch pointer events (no hover; :active, the gesture arena and the click as for a finger), like Playwright's tap. The element's OnClick runs, with a touch pointer in its event. It waits for the element to be visible, enabled, in view and not covered, and fails the test when l matches none or several.

app.Tap(telatest.ByRole("button", "Save"))

func (*App) Title

func (a *App) Title() string

Title returns the window's title, as the OS shows it: tela.App.Title or tela.Window.Title, as of the latest build.

func (*App) TitleBar

func (a *App) TitleBar() tela.TitleBarColors

TitleBar returns the title bar colors the window has asked the OS for (tela.App.TitleBar, tela.Window.TitleBar), at the 8-bit precision the OS takes; a color that is the system's is the zero Color.

func (*App) Trashed

func (a *App) Trashed() []string

Trashed returns the paths the app moved to the trash with desktop.MoveToTrash, in order, after settling the app. The headless trash moves each into a temporary folder of the test, so the file is gone from its place, as with a real trash; failed calls are not listed.

app.Click(telatest.ByRole("button", "Move to Trash"))
app.ExpectText("Moved todo.md to the Trash")
if got := app.Trashed(); len(got) != 1 || got[0] != path {
    t.Errorf("trashed %v", got)
}

func (*App) Tray

func (a *App) Tray() TrayIcon

Tray returns the app's tray icon; it fails the test unless the app shows exactly one.

func (*App) Trays

func (a *App) Trays() []TrayIcon

Trays returns the tray icons the app shows now, in the order they were added, after settling the app.

func (*App) Type

func (a *App) Type(text string)

Type types text one character at a time, as key presses and releases sent to the focused element: each has the character's physical Code (US layout, when it has one), Key and Text, and no modifiers (an upper case letter is not typed with Shift, as in Playwright's keyboard.type). A newline types Enter and a tab Tab. In a focused text field each key inserts its text through the real key path (OnKeyDown, then OnBeforeInput and OnChange), as a user typing; App.Fill replaces the value at once.

func (*App) Unmaximize

func (a *App) Unmaximize()

Unmaximize restores a maximized window to its size.

func (*App) Unsupported

func (a *App) Unsupported(services ...DesktopService)

Unsupported makes the headless desktop lack the given services from now on, as a desktop without them does (Linux without a portal, GNOME without a tray): the calls return errors wrapping desktop.ErrUnsupported, and ShowMessage draws its box. Called with nothing, every service is supported again.

app := telatest.Mount(t, Editor{})
app.Unsupported(telatest.FileDialogs)
app.Click(telatest.ByRole("button", "Open…"))
app.ExpectText("Type the path of the file")

func (*App) WaitFor

func (a *App) WaitFor(what string, cond func() bool)

WaitFor waits until cond reports true, settling the app and letting a frame of app time and a moment of real time pass between tries, until the timeout (see Timeout); under NoSettle the first try handles only the work due, and the clock moves only between failed tries. It is for conditions the locators cannot see: a value computed from App.Bounds, or Go state that a goroutine sets. what names the condition in the failure message ("the progress bar reached 3 s"). cond runs on the test goroutine.

func (*App) WaitForWindow

func (a *App) WaitForWindow(open func()) *App

WaitForWindow runs open, which should make the app open a window (a click, a key press, a goroutine started by one), and returns the App of the window that opened as a result, waiting for it: Playwright's page.waitForEvent('popup'). When several windows opened it returns the first of them. It fails the test at the timeout.

settings := app.WaitForWindow(func() {
    app.Click(telatest.ByRole("button", "Settings"))
})
settings.ExpectText("Preferences")

Example

var clicks int
var cleaned []string
app := mountNotes(t, &clicks, &cleaned)

settings := app.WaitForWindow(func() {
    app.Click(telatest.ByRole("button", "Settings"))
})
settings.ExpectText("Preferences")
settings.Click(telatest.ByRole("button", "Applied 0"))
settings.ExpectText("Applied 1")
fmt.Println(settings.Title(), settings.Modal(), app.Blocked(), len(app.Windows()))
// Output: Settings true true 2

func (*App) Window

func (a *App) Window(title string) *App

Window returns the open window whose title is title (exactly), waiting until there is one, as Playwright waits for a locator. It fails the test at the timeout, listing the titles of the open windows, and at once when several windows have the title (select one with App.WindowAt).

settings := app.Window("Settings")
settings.Click(telatest.ByRole("button", "Close"))

func (*App) WindowAt

func (a *App) WindowAt(i int) *App

WindowAt returns the open window at index i in the order the windows opened (0 is the main window), waiting until there are that many.

func (*App) WindowMenus

func (a *App) WindowMenus() int

WindowMenus returns how many secondary presses in a caption region opened the window menu of the fake window manager.

func (*App) WindowSize

func (a *App) WindowSize() (width, height float32)

WindowSize returns the size of the window's content area in logical pixels, as the app was told (a maximized window has the fake screen's size, see Screen).

func (*App) WindowState

func (a *App) WindowState() tela.WindowState

WindowState returns the window's state as the app sees it (tela.WindowHandle.State): maximized, fullscreen and minimized as the fake window manager reported them, whether the window is the active one, whether the app hid it, and whether the app draws its title bar.

func (*App) Windows

func (a *App) Windows() []*App

Windows returns the app's open windows in the order they opened, the main window first: an App scoped to each, with the same queries, actions, expectations and goldens as the one Mount returns. A window that is hidden (the app's Hidden, a hidden Window) or minimized is still open and listed. The app is settled first, so a window a handler just asked for is listed; use App.WaitForWindow or App.ExpectWindowCount for one that opens later (a goroutine, a timer). Like Playwright's context.pages().

for _, w := range app.Windows() {
    t.Logf("%q is %v", w.Title(), w.WindowState())
}

type ClickOption

type ClickOption func(*clickConfig)

ClickOption configures App.Click and App.ClickAt.

func Delay

func Delay(d time.Duration) ClickOption

Delay holds the button down for d of app time between the press and the release, like Playwright's click delay. It is how a test makes a long press of the mouse (the arena recognizes one after 500 ms): the handler runs while the button is held and the release then ends the press without a click. For a touch long press, which opens a context menu, use App.LongPress.

func Right

func Right() ClickOption

Right makes App.Click and App.ClickAt press and release the secondary (right) mouse button, like Playwright's click({button: 'right'}): real pointer events with the secondary button, so the element's OnContextMenu runs once the button is released (a kit.ContextMenu opens) and its OnClick does not. The element must be as actionable as for a primary click: visible, enabled, in view and not covered. It combines with Delay.

app.Click(telatest.ByTestID("area"), telatest.Right())
app.ClickAt(120, 80, telatest.Right())

type DesktopService

type DesktopService uint8

DesktopService is a group of desktop services, for App.Unsupported.

const (
    // FileDialogs: desktop.OpenFile, OpenFiles, SaveFile, PickFolder.
    FileDialogs DesktopService = iota + 1
    // MessageBoxes: the native message box of desktop.ShowMessage; without
    // it the box is drawn in a window, as on Linux.
    MessageBoxes
    // Notifications: desktop.Notify.
    Notifications
    // TrayIcons: desktop.Tray.
    TrayIcons
    // OpenURLs: tela.OpenURL and tela.OpenPath.
    OpenURLs
    // NativeMenus: desktop.PopupMenu and desktop.MenuBar, as on Linux
    // (the app draws its menus instead).
    NativeMenus
    // GlobalShortcuts: desktop.GlobalShortcut (OnStatus gets
    // ShortcutFailed), as on Wayland without the portal.
    GlobalShortcuts
    // Secrets: desktop.SetSecret, Secret and DeleteSecret, as on a Linux
    // session without a keyring.
    Secrets
    // ClipboardData: desktop.ReadClipboard, WriteClipboard and
    // ClipboardTypes (the text functions keep working).
    ClipboardData
    // Associations: desktop.Register, Unregister, MakeDefault,
    // RegistrationStatus, RegisterApp, UnregisterApp and AppStatus while
    // the app runs, as on macOS before its desktop integration.
    Associations
    // Trash: desktop.MoveToTrash, as on a desktop without a trash or for
    // a file the trash cannot take (a network drive); the file stays.
    Trash
)

type DragInfo

type DragInfo struct {
    // Data is a copy of what the drag carries, with every getter open
    // (Types, Text, HTML, Files, Image, GetData...). Its EffectAllowed is
    // Allowed.
    Data *tela.DataTransfer
    // Allowed are the effects the source allows.
    Allowed tela.DropEffect
    // External: another application's drag. Source is the window of the
    // app that started it, nil for an external drag.
    External bool
    Source   *App
    // Over is the window the drag is over, nil while it is over none.
    Over *App
}

DragInfo describes the drag in progress (App.ActiveDrag).

type DragOption

type DragOption func(*dragConfig)

DragOption configures App.Drag, App.DragBy and App.DragFromTo.

func Allowed

func Allowed(effects ...tela.DropEffect) DragOption

Allowed sets the effects the other application allows for the drag that Carrying starts (default: copy, move and link). The app's DataTransfer.EffectAllowed in its handlers reports them, and a drop refuses an effect outside them.

func Carrying

func Carrying(payload DragPayload) DragOption

Carrying makes the step start another application's drag of payload when no drag is in progress, instead of failing. App.HoverDrag and App.DropOn take it; App.DropData and App.DropFiles are DropOn with it.

func Modifiers

func Modifiers(keys ...string) DragOption

Modifiers holds keys during the step, with Playwright's names ("Shift", "Control", "Alt", "Meta", "ControlOrMeta"). The platform's convention picks the default DropEffect from them: Control copies, Shift moves, Control+Shift links.

func Steps

func Steps(n int) DragOption

Steps sets how many pointer moves the drag is made of (default 12): the pointer moves in equal steps between the start and the end, and each move runs a frame, as a window would.

type DragPayload

type DragPayload struct {
    // contains filtered or unexported fields
}

DragPayload is the data another application drags: files, text, HTML, a PNG image or a custom type, in one or more representations. Build it with DragFiles, DragText, DragHTML, DragPNG and DragType, and combine representations, best first, with DragPayload.And.

func DragFiles

func DragFiles(paths ...string) DragPayload

DragFiles is a list of files, as a file manager drags them: the app reads their paths with DataTransfer.Files. The paths must be absolute (use t.TempDir()); the files need not exist.

func DragHTML

func DragHTML(html string) DragPayload

DragHTML is an HTML fragment (the "text/html" type). Add DragText with DragPayload.And for applications that read only text.

func DragPNG

func DragPNG(img image.Image) DragPayload

DragPNG is an image, carried as "image/png".

func DragText

func DragText(s string) DragPayload

DragText is plain text (the "text/plain" type).

func DragType

func DragType(mime string, data string) DragPayload

DragType is data of a custom MIME type, such as "application/x-card" (HTML's setData). It panics when mime is not a MIME type.

func (DragPayload) And

func (p DragPayload) And(other DragPayload) DragPayload

And returns the payload with the representations of other added after its own. It panics when both carry the same type.

telatest.DragHTML("<b>Hi</b>").And(telatest.DragText("Hi"))

type FileDialog

type FileDialog struct {
    Kind FileDialogKind
    // Title, Directory and DefaultName are the options the app gave
    // (desktop.Title, Directory, DefaultName).
    Title, Directory, DefaultName string
    // Filters are the desktop.Filter entries.
    Filters []desktop.FileFilter
}

FileDialog is a file dialog the app opened and the test has not answered.

type FileDialogKind

type FileDialogKind string

FileDialogKind is the kind of a FileDialog.

const (
    OpenFileDialog   FileDialogKind = "OpenFile"   // desktop.OpenFile
    OpenFilesDialog  FileDialogKind = "OpenFiles"  // desktop.OpenFiles (several files)
    SaveFileDialog   FileDialogKind = "SaveFile"   // desktop.SaveFile
    PickFolderDialog FileDialogKind = "PickFolder" // desktop.PickFolder
)

type FilterOption

type FilterOption func(*Locator)

FilterOption narrows a locator in Locator.Filter.

func HasClass

func HasClass(classes ...string) FilterOption

HasClass keeps the elements that have all the given Tailwind class names. Like ByClass it ties the test to the styling.

func HasText

func HasText(text string) FilterOption

HasText keeps the elements whose text content contains text (a case-insensitive substring, whitespace normalized), like Playwright's filter({hasText}).

type GlobalShortcut

type GlobalShortcut struct {
    ID, Description string
    // Keys is the combination asked for, written the usual way
    // ("Ctrl+Alt+N").
    Keys string
    // Status and Trigger are the latest outcome the app was told
    // (ShortcutStatusEvent).
    Status  desktop.ShortcutStatus
    Trigger string
}

GlobalShortcut is a global shortcut the app registered (a mounted desktop.GlobalShortcut).

type GoldenOption

type GoldenOption struct {
    // contains filtered or unexported fields
}

GoldenOption configures App.Golden.

func Tolerance

func Tolerance(maxDelta uint8, maxPixels int) GoldenOption

Tolerance lets a golden comparison accept small differences: pixels whose channels differ by at most maxDelta (0 to 255, on premultiplied values) count as equal, and up to maxPixels pixels may differ by more. Without it a golden must match bit by bit: Tela's pixels are the same on every platform (ADR-027), so only a deliberate change should alter one.

type HitKind

type HitKind = platform.HitKind

HitKind is what a region of a window's custom title bar is to the OS.

HitKind is an alias of platform.HitKind, declared in an internal package of the module. pkg.go.dev shows only the alias; the exported fields and methods of the aliased type, listed here, are part of the API:

type HitKind uint8
const (
    // HitClient is ordinary content. It is what lies outside every
    // region, and a HitClient region punches a hole into the regions
    // listed before it (a search field or a tab strip inside a caption
    // area).
    HitClient HitKind = iota
    // HitCaption is a drag area of the title bar: the OS handles presses
    // in it (move, double-click, window menu).
    HitCaption
    // HitMinimize, HitMaximize and HitClose are the window buttons drawn
    // by the app. Pointer events over them reach the app as usual (its
    // buttons perform the action); the region tells the OS what they are
    // (Windows 11 snap layouts open from the maximize button).
    HitMinimize
    HitMaximize
    HitClose
)

func (HitKind) String

func (k HitKind) String() string

type HitRegion

type HitRegion struct {
    Kind HitKind
    Rect tela.Rect
}

HitRegion is one rectangle of a window's custom title bar, in logical pixels from the window's top-left corner.

func (HitRegion) String

func (r HitRegion) String() string

type Locator

type Locator struct {
    // contains filtered or unexported fields
}

Locator describes the element an action or expectation works on, like a Playwright locator: it is resolved against the live semantics tree each time it is used, so it keeps working when the app rebuilds. Make one with ByRole, ByText, ByClass or ByTestID and narrow it with Locator.Filter and Locator.Nth.

Hidden elements (display: none, visibility other than visible, AriaHidden, and their descendants) are never matched. An action needs the locator to match exactly one element (strictness, as in Playwright) and fails with the aria snapshot and the closest names when it matches none or several.

func ByClass

func ByClass(classes ...string) Locator

ByClass locates the elements that have all the given Tailwind class names. It is an escape hatch for elements without a role or text, and ties the test to the styling: prefer ByRole and ByText.

telatest.ByClass("rounded-full", "shadow-sm")

Example

fmt.Println(telatest.ByClass("rounded-full", "shadow-sm"))
// Output: getByClass("rounded-full shadow-sm")

func ByRole

func ByRole(role, name string) Locator

ByRole locates the elements with an ARIA role (explicit with h.Role or implicit: button, heading, link, checkbox...) and, when name is not empty, exactly this accessible name (whitespace normalized, case sensitive). Elements without a role of their own (Div, Span) are never matched by role, as in Playwright. An empty name matches any name.

telatest.ByRole("button", "Increment")
telatest.ByRole("heading", "")

Example

fmt.Println(telatest.ByRole("button", "Increment"))
fmt.Println(telatest.ByRole("heading", ""))
// Output:
// getByRole("button", name="Increment")
// getByRole("heading")

func ByTestID

func ByTestID(id string) Locator

ByTestID locates the element that carries the h.TestID with this exact id, like Playwright's getByTestId. It is the way to find an element a user cannot name (a card, a chart, a drop zone); the id is not part of the semantics tree or the aria snapshot, so it is never visible to assistive technologies. Hidden elements are not matched, like with the other locators.

telatest.ByTestID("sidebar")

Example

card := func() tela.Node {
    return Div(TestID("card"), Class("size-16 bg-slate-200"))
}
app := telatest.MountFunc(t, card, telatest.Size(100, 100))

fmt.Println(telatest.ByTestID("card"))
fmt.Println(app.Bounds(telatest.ByTestID("card")))
fmt.Println(app.AriaSnapshot() == "") // the id is not in the snapshot
// Output:
// getByTestId("card")
// {0 0 64 64}
// true

func ByText

func ByText(text string, opts ...TextOption) Locator

ByText locates the elements that contain text, like Playwright's getByText: it matches the text content of an element (whitespace normalized, hidden descendants left out, block boundaries counting as spaces) and picks the deepest elements that match, so the button that holds the text rather than the page that holds the button. The match is a case-insensitive substring unless Exact is given.

telatest.ByText("Count: 1")
telatest.ByText("Save", telatest.Exact())

Example

fmt.Println(telatest.ByText("Count"))
fmt.Println(telatest.ByText("Save", telatest.Exact()))
// Output:
// getByText("Count")
// getByText("Save", exact)

func (Locator) Filter

func (l Locator) Filter(opts ...FilterOption) Locator

Filter returns a locator that matches only the elements of l that satisfy the options.

telatest.ByRole("button", "").Filter(telatest.HasText("System"))

Example

rows := telatest.ByRole("button", "").Filter(telatest.HasText("System"))
fmt.Println(rows)
// Output: getByRole("button").filter(hasText="System")

func (Locator) Nth

func (l Locator) Nth(i int) Locator

Nth returns a locator for the i-th match of l (0 is the first; a negative i counts from the end, so -1 is the last). It turns a locator that matches several elements into one that matches one.

Example

fmt.Println(telatest.ByRole("listitem", "").Nth(0))
fmt.Println(telatest.ByRole("listitem", "").Nth(-1)) // the last one
// Output:
// getByRole("listitem").nth(0)
// getByRole("listitem").nth(-1)

func (Locator) String

func (l Locator) String() string

String describes the locator the way Playwright does.

type MenuItem

type MenuItem struct {
    Label, Accelerator           string
    Separator, Checkbox, Checked bool
    Disabled                     bool
    Submenu                      []MenuItem
}

MenuItem is an item of a native menu: a popup menu (desktop.PopupMenu) or a window's menu bar (desktop.MenuBar).

type MessageBox

type MessageBox struct {
    Kind    desktop.MessageKind
    Title   string
    Text    string
    Buttons desktop.Buttons
    // Drawn: Tela draws the box in a window (the desktop has no native
    // message box, as on Linux; see [App.Unsupported]).
    Drawn bool
}

MessageBox is a message box the app shows (desktop.ShowMessage) and the test has not answered.

type Notification

type Notification struct {
    Title, Body string
    // Actions are the labels of its buttons.
    Actions []string
    // Closed: the app closed it, or the user clicked or dismissed it.
    Closed bool
}

Notification is a notification the app showed (desktop.Notify).

type Option

type Option func(*config)

Option configures Mount and the headless entry points Render, AriaSnapshot and LayoutJSON.

func AccentColor

func AccentColor(color tela.Color) Option

AccentColor sets the system's accent color, which tela.UseAccentColor returns (by default the system has none).

func AllowDev

func AllowDev(substrings ...string) Option

AllowDev lists dev errors that do not fail a test: those containing any of the substrings, which are logged instead. Dev warnings never fail a test, and AllowDev does nothing under NoDev.

func AlwaysOnTop

func AlwaysOnTop() Option

AlwaysOnTop keeps the main window above the others (tela.App.AlwaysOnTop).

func AppID

func AppID(id string) Option

AppID sets the application identity the window reports to the desktop (tela.App.AppID).

func Backdrop

func Backdrop(b tela.Backdrop) Option

Backdrop sets the system backdrop of the main window (tela.App.Backdrop). The fake window manager only records it: App.Backdrop reads it back.

func CheckDamage

func CheckDamage() Option

CheckDamage makes the app compare its window with a full rendering after every frame (when nothing is left to draw), and fail the test on the first pixel that differs: frames are rasterized incrementally (only what changed, scrolled content shifted), and must give the pixels a full rasterization gives. It makes every frame cost a full rendering; it is for tests of rendering, and TELA_CHECK_DAMAGE=1 turns it on everywhere.

func ClassAliases

func ClassAliases(aliases map[string]string) Option

ClassAliases sets the app's class aliases, like tela.App.ClassAliases: class names that expand to class strings where they appear.

func ClassicScrollbars

func ClassicScrollbars() Option

ClassicScrollbars draws always-visible classic scrollbars that take space (tela.App.ClassicScrollbars).

func ColorScheme

func ColorScheme(s tela.ColorScheme) Option

ColorScheme sets the app's color scheme, like tela.App.ColorScheme: tela.Light or tela.Dark override the system's preference (Dark sets that one), tela.System follows it. tela.SetColorScheme changes it while the app runs.

Example

swatch := Div(Class("size-8 bg-white dark:bg-black"))
light, _ := telatest.Render(swatch, telatest.Size(8, 8), telatest.Dark(), telatest.ColorScheme(tela.Light))
dark, _ := telatest.Render(swatch, telatest.Size(8, 8), telatest.ColorScheme(tela.Dark))
fmt.Println(light.RGBAAt(4, 4), dark.RGBAAt(4, 4))
// Output: {255 255 255 255} {0 0 0 255}

func Dark

func Dark() Option

Dark makes the system prefer the dark color scheme, so the dark: variants apply unless the app sets another scheme (ColorScheme, tela.App.ColorScheme, tela.SetColorScheme).

func Debug

func Debug(flags ...string) Option

Debug turns debug overlays on: any of "layout", "paint-flash", "layers", "fps" and "semantics" (or "all"), the values TELA_DEBUG takes. The overlays are drawn over the window, so App.Screenshot and App.Golden show them; the headless entry points (Render) draw them too. TELA_DEBUG itself is ignored, so goldens never depend on the environment. An unknown name fails the test with the valid ones.

A paint flash fades over 300 ms of app time, and the fps meter redraws on every frame; neither keeps an action waiting (they are not work the app must finish), so look at them with App.Advance. See DebugClock for the frame times the meter shows.

func DebugClock

func DebugClock(now func() time.Duration) Option

DebugClock replaces the clock the fps overlay times frames with: a frame took the difference between two readings of now, taken before and after it. The default reads 1 ms longer at every call, so every frame takes 1 ms and goldens do not depend on the machine; tests of the meter pass a scripted clock.

func Dev

func Dev() Option

Dev turns dev mode on. It is the default since dev mode became the default (see NoDev), so Dev changes nothing; it is kept for the tests written before, and undoes a NoDev given earlier in the options.

func FixedSize

func FixedSize() Option

FixedSize keeps the user from resizing the main window (tela.App.FixedSize).

func Frameless

func Frameless() Option

Frameless gives the main window no OS title bar (tela.App.Frameless): its tela.WindowState reports ClientSideDecorations, and the content draws its own title bar with h.WindowDrag and h.WindowControl.

func FromApp

func FromApp(app tela.App) Option

FromApp applies the settings of a tela.App that shape what is drawn, so a test, tela snapshot and the window see the same app: the size when both Width and Height are set, RootClass, ColorScheme, Theme, ClassAliases, ClassicScrollbars, InstantScroll, NativeMenus, IgnoreTextScale and Dev. It sets only what the app sets, so it never undoes an earlier option, and the options after it override it:

var App = tela.App{Title: "Notes", Width: 900, Height: 600, RootClass: "bg-background", Root: Notes()}

app := telatest.Mount(t, Notes(), telatest.FromApp(App))
app := telatest.Mount(t, Notes{}, telatest.FromApp(App), telatest.Size(400, 300))

Title, AppID, Root, DevState and the fields of the OS window (size limits, FixedSize, Frameless, AlwaysOnTop, Hidden, Icon, Placement, OnClose) say nothing about how the content looks, so FromApp ignores them: tools that only render the app (tela snapshot, tela mcp) want it visible and quit-able whatever the app does with its window. MountApp mounts Root and applies those fields too, as the options Title, MinSize, Frameless, StartHidden, Placement, OnClose and the others do by hand. The scale is not an App setting (it comes from the display): use Scale.

Example

appSettings := tela.App{Width: 400, Height: 300, RootClass: "bg-slate-100"}
// Mount a struct component (or a tela.Preview) in the app's window.
app := telatest.Mount(t, tela.C(Counter), telatest.FromApp(appSettings), telatest.Scale(2))

fmt.Println(app.Screenshot().Bounds().Size())
// Output: (800,600)

func HighContrast

func HighContrast() Option

HighContrast makes the system ask for high contrast: the contrast-more: variants apply and tela.UseHighContrast reports true.

func Icon

func Icon(images ...image.Image) Option

Icon sets the icon of the main window (tela.App.Icon): the same picture at several sizes.

func InstantScroll

func InstantScroll() Option

InstantScroll makes user scrolling jump, without animation or flings (tela.App.InstantScroll).

func MaxSize

func MaxSize(width, height float32) Option

MaxSize limits the size of the main window from above; see MinSize.

func MinSize

func MinSize(width, height float32) Option

MinSize and MaxSize limit the size the user can give the main window (tela.App.MinWidth and MinHeight, MaxWidth and MaxHeight); zero is no limit on that side. The fake window manager clamps App.Resize, maximizing and fullscreen to them.

func NoDev

func NoDev() Option

NoDev turns dev mode off. Apps and the headless entry points run in dev mode by default, as a development build of an app does: unknown classes, roles, input types and easings, hooks called in another order or outside their component, and the dev warnings (lists without keys, nil dependencies, a Value read while changes are queued, a Label without a control...) are reported. With Mount, a dev error (an unknown class, an image that cannot be loaded) fails the test at the action that caused it unless AllowDev lists it, a dev panic fails it as any panic does, and a warning is a test log line; with Render and the other entry points, errors and warnings go to standard error.

Use NoDev to test what a release build does. The checks of the node constructors (h.If with a struct component, Role, Type, Dir...) follow the app's dev mode when they run while a component builds, as in a function given to MountFunc; a node built outside any build (the root passed to Mount in the test's body) follows the process default, which is on in a test binary. TELA_DEV=1 or TELA_DEV=0 in the environment overrides both NoDev and the default.

Example

// A typo in a class: in dev mode (the default) it fails the test with
// the closest class, "rounded-2xl".
card := func() tela.Node { return Div(Class("rounded-xxl p-4"), Text("Card")) }

// NoDev tests what a release build does: the class is ignored and the
// test goes on.
app := telatest.Mount(t, tela.C(card), telatest.Size(200, 100), telatest.NoDev())
app.ExpectText("Card")
fmt.Println("ok")
// Output: ok

func NoServerDecorations

func NoServerDecorations() Option

NoServerDecorations makes the fake window manager draw no title bars, as GNOME on Wayland does: every window, framed or not, reports ClientSideDecorations, so a test can check that the app draws its own bar (kit.TitleBar) there.

func NoSettle

func NoSettle() Option

NoSettle makes the app keep its animations where they are for App.Screenshot and App.Golden, so a test can capture the frames of a transition, a tela.UseSpring or the enter and exit of an h.Presence:

app := telatest.Mount(t, Dialog(), telatest.NoSettle())
app.Click(telatest.ByRole("button", "Open"))
app.Advance(100 * time.Millisecond)
app.Golden("dialog_100ms.png") // 100 ms into the entrance

With it, Mount, the events of an action, Screenshot and Golden and the queries App.AriaSnapshot, App.Count, App.Bounds, App.Stats and App.Cursor handle the work due (state updates, the frames they ask for) but do not run the clock until running transitions and animations end: the clock moves with App.Advance. The expectations poll as ever, but their first try handles only the work due, and the clock moves a frame at a time only between failed tries. What keeps its full auto-wait is the wait before an action: before acting, an action waits for the app to settle (so the animations an earlier step started end first).

Example

// A box that fades in over 200 ms when it is shown.
page := func() tela.Node {
    shown, setShown := tela.UseState(false)
    return Div(
        Button(OnClick(func(tela.MouseEvent) { setShown.Set(true) }), Text("Show")),
        If(shown, Div(Class("size-10 bg-black transition-opacity duration-200 ease-linear starting:opacity-0"))))
}
app := telatest.Mount(t, tela.C(page), telatest.Size(100, 100), telatest.NoSettle())
app.Click(telatest.ByRole("button", "Show"))
app.Advance(100 * time.Millisecond)
// Halfway: black at 50% over white. app.Golden("fade_100ms.png") would
// capture the same frame.
fmt.Println(app.Screenshot().RGBAAt(5, 40))
app.Advance(100 * time.Millisecond)
fmt.Println(app.Screenshot().RGBAAt(5, 40))
// Output:
// {128 128 128 255}
// {0 0 0 255}

func OnClose

func OnClose(f func(tela.WindowHandle)) Option

OnClose sets what closing the main window does (tela.App.OnClose): f runs when the user asks to close it (App.RequestClose, a WindowClose control, WindowHandle.Close). Without it the app quits.

func Placement

func Placement(p tela.WindowPlacement) Option

Placement opens the main window where a previous run left it (tela.App.Placement): a tela.WindowPlacement saved from tela.WindowHandle.Placement or App.Placement. Its size, when set, wins over Size.

func PlatformKeys

func PlatformKeys(goos string) Option

PlatformKeys makes the app follow the key conventions of the platform goos names, a runtime.GOOS value: "darwin" for macOS, "windows" for Windows, anything else for Linux (ADR-221). The key bindings and word rules of text fields follow it (Command+A selects all, Option+Left moves by word under "darwin"), and so do the events' CmdOrCtrl and the aria hooks built on it (Command+click toggles a selection on a Mac), and App.Press and Modifiers resolve "ControlOrMeta" to Meta under "darwin" and to Control otherwise. Without it the app follows the OS the test runs on. It applies to one app and its windows, so parallel tests with different platforms do not interfere:

app := telatest.Mount(t, Editor(), telatest.PlatformKeys("darwin"))
app.Press("ControlOrMeta+a") // Meta+a: selects all
app.Press("Alt+ArrowLeft")   // Option+Left: one word back

Only the conventions change: the headless backend, the look of the app and what the app reads from runtime.GOOS stay those of the host.

func PreferNativeMenus

func PreferNativeMenus() Option

PreferNativeMenus makes kit's ContextMenu and Menubar use the OS's menus where the platform has them (tela.App.NativeMenus, ADR-215). The headless desktop has them, unless App.Unsupported takes them away (NativeMenus) or the window is Frameless (no menu bar). Answer a native context menu with App.ChoosePopupItem and read a native bar with App.MenuBar.

func ReducedMotion

func ReducedMotion() Option

ReducedMotion makes the system ask for reduced motion: the motion-reduce: variants apply, transitions take no time and scrolling jumps.

func RootClass

func RootClass(classes string) Option

RootClass sets the classes of the root box, which fills the window (tela.App.RootClass): a background, a text style, overflow-hidden.

func Scale

func Scale(scale float64) Option

Scale sets the display scale: device pixels per logical pixel (default 1). Screenshots and goldens are (width*scale) x (height*scale) pixels.

func Screen

func Screen(width, height float32) Option

Screen sets the logical size of the fake screen: what maximized and fullscreen windows grow to (default 1920 by 1080).

func Size

func Size(width, height float32) Option

Size sets the logical size of the window in pixels (default 800x600).

func StartHidden

func StartHidden() Option

StartHidden opens the main window hidden (tela.App.Hidden): an app that starts in the tray. The fake window manager gives a hidden window no frames, so it draws nothing (no screenshot, no goldens, no actions that need a layout) until the app shows it; its components still build and its effects run (its tray icon shows, ADR-185).

func TextScale

func TextScale(scale float64) Option

TextScale sets the system's text scale (default 1): 1rem is the theme's rem times scale, so text and rem-based spacing grow (tela.UseTextScale), unless the app sets tela.App.IgnoreTextScale. The scale is above 0 and at most 4.

func Theme

func Theme(t tela.Theme) Option

Theme sets the app's theme, like tela.App.Theme: the color tokens (bg-primary, text-muted-foreground), the base radius and the fonts. Without it the app has tela.DefaultTheme. tela.SetTheme changes it while the app runs.

func Timeout

func Timeout(d time.Duration) Option

Timeout sets how long actions and expectations wait for their condition before they fail the test (default DefaultTimeout). It is wall-clock time, as in Playwright, while the app's clock moves one frame at a time during the wait. It also bounds, in app time (up to DefaultTimeout), how long a single action lets the app run before it stops waiting for it to go quiet (an animation that never ends is logged, not waited for); a finite animation runs to its end however slow the machine is.

func Title

func Title(title string) Option

Title sets the title of the main window (tela.App.Title), which App.Title and App.Window read. The default is "telatest".

func TitleBar

func TitleBar(colors tela.TitleBarColors) Option

TitleBar sets the title bar colors of the main window (tela.App.TitleBar). The fake window manager only records them: App.TitleBar reads them back.

type PopupMenu

type PopupMenu struct {
    // At is the menu's top-left corner in its window, logical pixels.
    At    tela.Point
    Items []MenuItem
}

PopupMenu is a native context menu the app shows (desktop.PopupMenu) and the test has not answered.

type Registration

type Registration struct {
    // Association is "notes:" for a scheme, ".note" for a file type.
    Association string
    // App is what was registered as the handler.
    App desktop.HandlerApp
    // Default: the app is the default handler.
    Default bool
}

Registration is a URL scheme or a file type the app registered (desktop.Register, MakeDefault), as the headless desktop recorded it.

type State

type State struct {
    // contains filtered or unexported fields
}

State is one condition on an element that App.Expect checks: a toggle such as Checked, a flag such as Disabled, its HasName and HasValue, or a widget property the aria snapshot does not print: HasPopup, ActiveDescendant and Controls (which compare the element an ID reference points to with the one a Locator finds). They read the element's semantics node, so they test what assistive technologies are told, without matching strings of the aria snapshot.

func ActiveDescendant

func ActiveDescendant(loc Locator) State

ActiveDescendant expects the element's aria-activedescendant to point to the element that loc locates (one element, strictness): the option a combobox or a list box keeps its virtual focus on while the keyboard focus stays on the input (h.AriaActiveDescendant sets it). Use NoActiveDescendant for none. The aria snapshot does not print it.

app.Expect(telatest.ByRole("combobox", "Project"),
    telatest.ActiveDescendant(telatest.ByRole("option", "users-db")))

func Checked

func Checked(checked bool) State

Checked expects the element to have aria-checked equal to checked (a checkbox, radio, switch or option; h.AriaChecked sets it). An element without the state, or with the mixed one, does not satisfy it.

func Controls

func Controls(loc Locator) State

Controls expects the element's aria-controls to point to the element that loc locates (one element, strictness): the list box of a combobox or the panel of a tab (h.AriaControls sets it). The aria snapshot does not print it.

app.Expect(telatest.ByRole("combobox", "Project"), telatest.Controls(telatest.ByRole("listbox", "Project")))

func Current

func Current(current string) State

Current expects the element's aria-current to be current: "page", "step", "location", "date", "time" or "true", or "false", which is also what an element without h.AriaCurrent has (the ARIA default). The aria snapshot does not print it (Playwright's does not either).

app.Click(telatest.ByRole("link", "Docs"))
app.Expect(telatest.ByRole("link", "Docs"), telatest.Current("page"))

func Disabled

func Disabled(disabled bool) State

Disabled expects the element to be disabled (true) or enabled (false): disabled by h.Disabled on it or an ancestor, or by h.AriaDisabled.

func Expanded

func Expanded(expanded bool) State

Expanded expects the element to have aria-expanded equal to expanded (what a button, link or combobox controls; h.AriaExpanded sets it).

func Focused

func Focused(focused bool) State

Focused expects the element to have the keyboard focus (true) or not (false).

func HasName

func HasName(name string) State

HasName expects the element's accessible name to be exactly name (white space normalized, case sensitive): what ByRole matches by.

func HasPopup

func HasPopup(kind string) State

HasPopup expects the element's aria-haspopup to be kind: "menu", "listbox", "tree", "grid" or "dialog" (h.AriaHasPopup sets it), or "" for an element that opens no popup. As in ARIA, "true" is "menu" and "false" is "". The aria snapshot does not print it (Playwright's does not either).

app.Expect(telatest.ByRole("button", "Show suggestions"), telatest.HasPopup("listbox"))

func HasValue

func HasValue(value string) State

HasValue expects the element's value to be exactly value: the text of a text field, or the value text of a range widget (the h.AriaValueText or h.AriaValueNow of a slider or progress bar, as in the aria snapshot).

func Invalid

func Invalid(invalid bool) State

Invalid expects the element's value to be invalid (true) or valid (false) for assistive technologies: h.AriaInvalid when it is set, else a text field's own validity (an email or url Input whose value does not parse; a ReadOnly or disabled field is never invalid by its value), as Chromium tells them.

app.Fill(telatest.ByRole("textbox", "Email"), "ada@")
app.Expect(telatest.ByRole("textbox", "Email"), telatest.Invalid(true))

func NoActiveDescendant

func NoActiveDescendant() State

NoActiveDescendant expects the element to have no active descendant (no aria-activedescendant, or one whose element is not in the tree).

func Pressed

func Pressed(pressed bool) State

Pressed expects the element to have aria-pressed equal to pressed (a toggle button; h.AriaPressed sets it).

func Selected

func Selected(selected bool) State

Selected expects the element to have aria-selected equal to selected (a tab, option, row or cell; h.AriaSelected sets it).

func Sort

func Sort(sort string) State

Sort expects the element's aria-sort to be sort: "ascending", "descending", "other" or "none", which is also what a header without h.AriaSort has (the ARIA default). The aria snapshot does not print it (Playwright's does not either).

app.Click(telatest.ByRole("columnheader", "Name"))
app.Expect(telatest.ByRole("columnheader", "Name"), telatest.Sort("descending"))

type Stats

type Stats = core.Stats

Stats counts the work of the app's tree since it was mounted: frames, component builds and skipped builds, style, layout and paint passes. A test uses the difference between two readings to assert how narrow an update was.

Stats is an alias of core.Stats, declared in an internal package of the module. pkg.go.dev shows only the alias; the exported fields and methods of the aliased type, listed here, are part of the API:

type Stats struct {
    Frames     int // Frame calls that ran the pipeline
    Builds     int // component builds
    Skips      int // component builds skipped (equal props, or the same node)
    Mounts     int // Elements mounted
    Unmounts   int // Elements unmounted
    Styles     int // style computations
    Structures int // box structure passes
    Shapes     int // paragraphs shaped (text.NewLayout)
    Breaks     int // paragraph line breakings (text.Layout.BreakLines)
    Layouts    int // layout passes (layout.Engine.Compute)
    Paints     int // paint passes
}

type TextOption

type TextOption func(*Locator)

TextOption configures ByText.

func Exact

func Exact() TextOption

Exact makes ByText match the whole text, case sensitive, instead of a case-insensitive substring.

type TrayIcon

type TrayIcon struct {
    Tooltip string
    Menu    []TrayItem
    // Updates counts the changes of the icon since it was added.
    Updates int
}

TrayIcon is a tray icon the app shows (a mounted desktop.Tray).

type TrayItem

type TrayItem struct {
    Label                        string
    Separator, Checkbox, Checked bool
    Disabled                     bool
    Submenu                      []TrayItem
}

TrayItem is an item of a tray icon's menu.

type WindowCond

type WindowCond struct {
    // contains filtered or unexported fields
}

WindowCond is a condition on a window's state for App.ExpectWindowState.

func Active

func Active(on bool) WindowCond

Active is met when the window is the active one, the one with the keyboard focus (WindowState.Focused), or is not.

func ClientDecorations

func ClientDecorations(on bool) WindowCond

ClientDecorations is met when the app draws the window's title bar (WindowState.ClientSideDecorations: a Frameless window), or does not.

func Fullscreen

func Fullscreen(on bool) WindowCond

Fullscreen is met when the window is (or is not) fullscreen.

func Hidden

func Hidden(on bool) WindowCond

Hidden is met when the app has hidden the window (tela.Window.Hidden, WindowHandle.Hide), or has not.

func Maximized

func Maximized(on bool) WindowCond

Maximized is met when the window is (or is not) maximized.

func Minimized

func Minimized(on bool) WindowCond

Minimized is met when the window is (or is not) minimized.

Clone this wiki locally