Skip to main content

write-tests

Author Biloba specs in a Go Ginkgo/Gomega suite — the dual immediate/matcher API (act now vs. return a matcher you poll with Eventually), capturing a matcher's observed value with .Capture instead of asserting-then-re-reading, first-vs-all naming, the navigate-then-readiness-anchor shape (gate on the DOM, then read GetLocation), selecting elements (CSS targeting stable hooks as the default, semantic role/text/label locators, anchoring a locator scope so a negative assertion isn't vacuous, the >>> piercing combinator, XPath), the interaction vocabulary (click variants, drag, scroll, tap, text selection), realistic mode for occlusion/hover smoke tests, visual regression with b.HaveScreenshot against a committed baseline, hermetic tests via request stubbing/aborting/modifying/holding, the GetJSValue app-state barrier (and when it gates nothing), multi-tab flows, cross-origin iframes (b.Frame frame handles), and seeding state. Use when writing or reviewing Biloba browser tests.

معلومات المصدر

المستودع
onsi/biloba
آخر نشاط في المصدر
١٥ سبتمبر ٢٠٢٦ في ١٤:٣٧
لغة SKILL.md المكتشفة
الإنجليزية
النجوم
٢٩
التفرعات
٩

خيارات التثبيت

يُحدَّد Prompt الذي يراجع المصدر أولًا بشكل افتراضي. يمكنك التبديل إلى أمر مباشر أو تنزيل نسخة محلية.

مراجعة ملفات المصدر

اقرأ SKILL.md وأي ملفات مرافقة يعرضها SkillsMP قبل أن تقرر التثبيت.

عرض SKILL.md

SKILL.md
تعليمات المصدر · معاينة للقراءة فقط
name
write-tests
description
Author Biloba specs in a Go Ginkgo/Gomega suite — the dual immediate/matcher API (act now vs. return a matcher you poll with Eventually), capturing a matcher's observed value with .Capture instead of asserting-then-re-reading, first-vs-all naming, the navigate-then-readiness-anchor shape (gate on the DOM, then read GetLocation), selecting elements (CSS targeting stable hooks as the default, semantic role/text/label locators, anchoring a locator scope so a negative assertion isn't vacuous, the >>> piercing combinator, XPath), the interaction vocabulary (click variants, drag, scroll, tap, text selection), realistic mode for occlusion/hover smoke tests, visual regression with b.HaveScreenshot against a committed baseline, hermetic tests via request stubbing/aborting/modifying/holding, the GetJSValue app-state barrier (and when it gates nothing), multi-tab flows, cross-origin iframes (b.Frame frame handles), and seeding state. Use when writing or reviewing Biloba browser tests.
# Writing Biloba specs Assume a **Go/Ginkgo** suite, wired with `setup`, and apply the principles in `overview`. Method list → `api`. XPath → `xpath`. Flakes → `flaky-specs`. Docs: <https://onsi.github.io/biloba/#working-with-the-dom>. Sibling skills are named here without a prefix; invoke one with the same plugin prefix you loaded this skill under. ## RULE — get these two right in the first draft 1. **Selecting elements.** User-facing things → a **Locator** by role/name/text (`b.ByRole("button").WithName("Save")`, `b.ByText("Sign in")`), which doubles as an a11y guard. Structural/state hooks you own → **CSS on a stable `#id`/`[data-testid]`**, never a styling class. 2. **Assert observable outcomes** — visible text, counts, URL/title, network effects — not internal class/structure. **Smells to catch in your own draft** (generic-automation muscle memory gets these wrong): | Smell | Instead | |---|---| | `:nth-of-type`, `.btn-primary` | `b.ByCSS(sel).Nth(i)`, or a stable `#id`/`[data-testid]` | | text-matching XPath | `b.ByText(...)` / `b.ByRole(...).WithName(...)` | | `b.Run("...querySelectorAll(s).length")` | `b.HaveCount(...)` — reinventing a matcher | | IIFE-wrapping a script to get `return` | `b.RunAsync` | | `SetValue` when you meant keystrokes | `b.Type` | | `b.Run(expr, &x)` then `Expect(x)` — single-shot read | `Eventually(b.Run).WithArguments(expr)` | | matcher assertion, then a getter for the same value | `.Capture(&x)` on the matcher — one read | | a guarded/conditional interaction ("click X only if…") | fix the spec's state determinism upstream → `flaky-specs` | ## The one pattern: dual, and poll-by-default Most DOM methods have **two forms keyed on argument count**. ```go // Fully applied → POLLS until ready, acts/reads exactly once, stops. The everyday form. b.Click("#go") // polls until clickable, clicks once b.SetValue("#name", "Jane") // polls until settable, sets once text := b.GetInnerText("#title") // polls until present, reads once // Under-applied → returns a matcher YOU poll. For a custom timeout, a Consistently, or .And(). Eventually("#go").Should(b.Click()) Eventually("#name").Should(b.SetValue("Jane")) Eventually("#title").Should(b.HaveInnerText("Welcome")) ``` A fully-applied action dispatches **once** on the first success and stops — safe even on a **toggle**, no oscillation, no "fired a frame too early" race. **Tune or opt out** with shallow `*Biloba` clones: `b.WithTimeout(5*time.Second).Click("#go")`, `b.WithPolling(d)`, `b.WithContext(ctx)`. `b.Immediate()` reverts to act-once/fail-fast — **you almost never want it**. Misapplying a knob (e.g. `WithPolling` on a snapshot, or any knob on the bare matcher form) is a hard error → four-bucket table in `api`. **First-vs-all naming.** A bare method acts on the **first** match. Plural getters are **snapshots** (no poll — gate presence first); plural actions carry an **`Immediately` suffix** (act now, no poll; the long suffix is a deliberate "know what you're doing" smell). Matchers over all matches are `Each*` and **fail on zero matches**. | first (polls) | all (snapshot / immediate) | matcher over all | |---|---|---| | `GetInnerText` | `CurrentInnerTextForEach` | `EachHaveInnerText` | | `GetProperty` | `CurrentPropertyForEach` | `EachHaveProperty` | | `Click` | `ClickEachImmediately` | — | `EachHaveInnerText`/`EachHaveTextContent`'s sub-matcher and `.Capture` see a typed `[]string` (same slice their `Current*ForEach` getter returns) — `Equal([]string{...})` works. The generic `EachHaveProperty` hands over the raw JSON-decoded `[]any` (properties are heterogeneous) — `Equal([]string{...})` always fails there; reach for `HaveExactElements(...)`. Same shape as `EvaluateTo`'s raw-vs-typed asymmetry, below. ## The spec shape ```go var _ = Describe("the search page", func() { BeforeEach(func() { b.Navigate("http://localhost:8080/search") Eventually("#results").Should(b.Exist()) // readiness anchor }) It("finds matches", func() { b.SetValue("#q", "biloba") b.Click(b.ByRole("button").WithName("Search")) Eventually(".result").Should(b.HaveCount(BeNumerically(">", 0))) }) }) ``` - `b.Navigate(url)` asserts the response was `200` (`NavigateWithStatus` for other codes). - Pick a **stable, meaningful** anchor (a heading, a key container) with `b.Exist()`/`b.BeVisible()`. **For an in-app navigation, gate on a DOM anchor and *then* read the location — don't poll the URL.** The URL flips the instant the router pushes it, so it's the weakest proof the destination rendered. ```go b.Click("#submit") Eventually("#dashboard-root").Should(b.Exist()) // proves the navigation landed… Ω(b.GetLocation()).Should(HaveSuffix("/dashboard")) // …so this is a formality ``` `b.GetLocation()`/`b.GetTitle()` are polling getters honoring all four knobs, and a transient CDP error mid-navigation (`Inspected target navigated or closed`) is a retryable miss — so `Eventually(b.GetLocation)` is safe, just weaker. ## Pocket matcher cheat-sheet — reach here before `b.Run` | Want to assert… | Matcher | |---|---| | present / visible | `b.Exist()` / `b.BeVisible()` | | how many match | `b.HaveCount(BeNumerically(">", 0))`; distinct by key: `b.HaveDistinctCount("data-key", 3)` | | visible text | `b.HaveInnerText("…")` / `b.HaveTextContent(…)` / `b.HaveText(…)` (whitespace-collapsed) | | a DOM/JS property | `b.HaveProperty("href", …)` / `b.HaveClass("active")`; JSON attr `b.HaveJSONAttribute("data-state", HaveKeyWithValue(…))` (getter `b.GetJSONAttribute(sel, attr, &out)`) | | actually clickable (visible+enabled+topmost) | `b.BeClickable()` | | form value | `b.HaveValue(…)` | | tab URL / title / spawned tab | `b.HaveURL(…)` / `b.HaveTitle(…)` / `b.HaveSpawnedTab(…)`; getters `b.GetLocation()`/`b.GetTitle()` | | the app applied a server response | `b.GetJSValue("window.__storeLog", &log)` — app-state barrier (below); *not* the DOM, *not* a Go-side HTTP read | | a network request was made | `Eventually(b).Should(b.HaveMadeRequest(…))` | | layout / box / scroll | `b.HaveBoundingBox(HaveField("Top", …))` / `b.HaveOffsetTopWithin(container, …)` / `b.HaveScrollOffset(…)`. `Box.Width`/`Height` = border-box; `ClientWidth`/`ClientHeight` = scrollbar-excluded content box | | A relative to B | `b.BeAbove` / `BeBelow` / `BeLeftOf` / `BeRightOf` / `b.Encloses` / `b.Overlaps`; numeric `b.GetGapBetween(a,b)` → `BoxDelta` | | on screen / document order | `b.BeInViewport()` (partial; `b.BeInViewport(b.Fully())` = whole box) / `b.BePrecededBy` / `b.BeFollowedBy` | | computed style | `b.HaveComputedStyle(prop, …)` / `b.HaveComputedStyleNumeric(prop, …)`; color across syntaxes `b.HaveComputedStyle(prop, b.MatchColor("var(--tok)"))`; getters `b.GetComputedStyle`/`GetComputedStyleNumeric`/`NormalizeColor` | | scroll a target into view (instant) | `b.ScrollIntoView(sel)` — options `b.WithinScroller(container)`, `b.AtTopOffset(px)` | | an arbitrary JS expression | `Eventually(expr).Should(b.EvaluateTo(matcher))` | | it still *looks* like this | `b.HaveScreenshot("name")` against a committed baseline → `visual-assertions` | - `EvaluateTo`/`Run` decode JS numbers to **float64** — `BeNumerically("==", n)`, not `Equal(intLiteral)`. - **`BePrecededBy`/`BeFollowedBy` read subject-first**: `Eventually(X).Should(b.BeFollowedBy(Y))` ⇔ X comes **BEFORE** Y. A backwards one passes silently — also assert the inverse doesn't hold. - **Geometry matchers error on a missing element**, so `Eventually(sel).ShouldNot(b.BeInViewport())` times out. Assert disappearance with `ShouldNot(b.Exist())` / `Should(b.HaveCount(0))`. ## `.Capture(&x)` — don't re-read what you just asserted on ```go var blockID string Eventually(".figure-frame").Should(b.HaveAttribute("data-block-id", Not(BeEmpty())).Capture(&blockID)) b.Click("#block-" + blockID) ``` Asserting and *then* calling the getter is two reads of a page that may have changed in between. `Capture` hands you the value from the winning read, decoded into your Go type, and writes only on a match (`ShouldNot` captures nothing). Value-less matchers (`Exist`, `BeVisible`, `BeInViewport`, the relational ones) and actions-as-matchers deliberately don't compile with it. Decode rules (typed-decode mismatches, absent-vs-zero via `**T`, the new-matcher-each-time behavior, narrowing a JS object vs. a Biloba struct) → `flaky-specs` §1. Full capturable list → `api`. ## Selecting elements — the vocabulary A `selector` is a **CSS string** (fastest; `:has()`; `>>>` pierces shadow/iframe), a **`Locator`** (`b.By*`; most resilient, slowest — full-document ARIA scan), or an **`XPath`** (`xpath`; axis/ordinal; pierces neither). ```go b.Click("#go") // CSS by id — the preferred default b.Click("[data-testid=save]") // CSS by intentional test-id Eventually("tr:has(td.overdue)").Should(b.Exist()) // CSS :has() — "the row that contains X" b.Click(b.ByRole("button").WithName("Save")) // Locator — role + accessible name (a11y guard) b.Click(b.ByText("Submit")) // Locator — visible text (ByTextContains for substring) b.SetValue(b.ByLabel("Email"), "jane@acme.com") // Locator — a control by its label b.Click(b.XPath("li").WithText("OK").Ancestor("ul"))// XPath — an axis query nothing else expresses ``` Constructors (each text-valued one has a `*Contains` variant): `b.ByRole`, `b.ByText`, `b.ByLabel`, `b.ByPlaceholder`, `b.ByAltText`, `b.ByTitle`, `b.ByTestID` (attr = `biloba.TestIDAttribute`, default `data-testid`), `b.ByCSS(sel)` (raw CSS into the algebra — the structural escape hatch). Refine a role with `.WithName(n)`, `.Level(n)`, or ARIA states `.Checked()`/`.Disabled()`/`.Expanded()`/`.Pressed()`/`.Selected()`. Locators **compose**, and every filter accepts **any** selector (CSS/XPath/Locator): ```go b.ByRole("listitem").ContainingText("Product 2") // .ContainingText / .NotContainingText b.ByRole("listitem").Containing(b.ByText("Delete")) // .Containing / .NotContaining (a descendant) b.ByRole("button").And(".primary") // .And / .Or — intersection / union b.ByRole("button").WithName("Delete").Within("#dialog") // .Within / .NotWithin b.ByText("Item").Nth(2) // .Nth(i) / .First() / .Last() b.ByCSS(".story").Nth(1) // the 2nd .story ``` **Trap — an unresolved `.Within`/`.Containing` scope matches nothing, so a negative assertion is vacuous** — it passes instantly and forever if the scope never renders. Anchor the scope first: ```go Eventually("#published-list").Should(b.Exist()) // the scope is real…
عرض على GitHub
ملف SKILL.md هذا كبير جدا، لذلك يعرض SkillsMP القسم الاول فقط هنا. عرض على GitHub