c4bcca06ef1a06c434c9136a79459f1512cd0606
braney
  Tue Sep 8 07:41:02 2026 -0700
docent: expect: can assert the color a track's row was drawn in, and a test for #36212

A bug about color leaves the page identical -- same rows, same height, same item
names, same tooltips -- so every check expect: had was blind to it. `color:` reads
the pixels instead: it names the color a row is mostly drawn in (`is:`), or one it
must not be (`not:`), with `part: label` for the center label rather than the
items, `at:`/`frac:`/`x:` for one item rather than the whole row, and a list form
so one step can state a whole color matrix and a failure name every row that came
out wrong.

hgTracks draws the whole view into one png and shows each row as a CSS-offset
slice of it, so a row's pixels are that slice drawn into a canvas at its offset.
The clipping box is the img's own div.sliceDiv, not the table cell: the center
label and the data are two slices inside one td_data_<key>, and measuring the cell
runs the canvas past the end of this row and into the next track's, which reads
that track's color as part of this one. The side labels are a separate png and are
never included, since "what color is this row" must not be answered by the label
text. White is background; everything else counts, black included, because a track
with no color of its own draws black items. is:/not: take r,g,b or #rrggbb and no
CSS color names, because trackDb's `color 0,255,0` is not CSS green.

tests/colorchecks covers all of it and tests/colorchecks.xfail aims all six forms
wrong in one step, so the failure has to name all six.

tests/regress/rm36212.xfail is the bug it was written for: a stanza that sets both
`itemRgb on` and `color` draws its items in the color setting instead of in the
file's own RGB column, because bedItemRgb() (hg/cgilib/bedCart.c) tests for the
presence of `color` before it tests for an explicit `itemRgb on`, so the explicit
setting is never reached. It is an xfail because the bug is live on the RR, on beta
and on genome-test.

It is also the first script in that directory that has been watched both to fail on
a build with the bug and to pass on a build with the fix -- the three-line reorder
built into parked #36212 and the same script pointed at that port. The fixture is
~/public_html/docentFixtures/itemRgbHub/, four tracks over four copies of one bed9
file whose items all carry a pure blue itemRgb column.

refs #36212, refs #37892

diff --git src/hg/utils/docent/tests/README.txt src/hg/utils/docent/tests/README.txt
index e8e240a92f8..e6d356abb28 100644
--- src/hg/utils/docent/tests/README.txt
+++ src/hg/utils/docent/tests/README.txt
@@ -1,112 +1,130 @@
 Docent tests
 ------------
 
 Run by hand, not by the kent tree's `make test`:
 
     make test               # every *.docent.yaml here
     make test T=composite   # just one
     make parity             # one script FAST and slow, and twice over
     make derive             # the derivation alone, against expected/ (no browser)
     make derive-accept      # rewrite those baselines, then read `git diff expected/`
 
 Most tests drive a real browser against a real server, so they need the network and
 the shared Playwright install (/hive/groups/browser/uiTest/pw; see ../README.md). That is why none of this is
 part of the tree-wide test target: a broken network would fail the build.
 
 A test is an ordinary Docent script that asserts with `expect:`. It passes by exiting
 0. `expect:` is the only verb that CHECKS anything, so a test with no `expect:` step in
 it tests nothing: `track:` accepts a name no assembly has and still exits 0.
 
 Other verbs do fail a run, so do not read the line above as "nothing else can stop it".
 A verb throws when it cannot do what it was told -- `mouseover:` cannot find the item,
 `loadSession:` cannot find the file, `drag:`, `convert:` and `go:` likewise -- and
 docent.js turns any step's throw into `step N (verb) failed` and exit 1. None of them
 looks at whether the page came out right, which is the part only `expect:` does.
 
 A script named *.xfail.docent.yaml is expected to FAIL, and the run fails if it passes.
 That is how a trap gets pinned rather than merely written down.
 
 `make derive` is the cheap half: DOCENT_DERIVE=1 resolves the `track:` steps against
 trackDb and prints the cart variables without opening a browser, in about a second. It
 is where Docent's own decisions live, and the baselines in expected/ are what catch a
 change to visVars() or tdbHideTargets() that a rendered page would hide.
 
 The trackDb listing is cached in $TMPDIR for a day (docent.js, TDB_TTL), and a cold
 fetch prints one provenance line that a warm run does not. That line would make the
 first `make derive` of the day differ from a baseline captured warm, for a reason that
 has nothing to do with trackDb changing, so the makefile strips it from both the run and
 the baseline. Everything else trackDb says about itself is kept, including the two lines
 that report a hub genome or an unreachable hubApi.
 
 What is covered
 ---------------
 
   selftest      session: -> expect: -> loadSession:, on hg38 at SHH. Saves the cart,
                 changes the view, restores it from the local file, checks rows both times.
   composite     clinvar with clinvarCnv hidden: the two-request split (#37953). One
                 request would leave clinvarCnv_sel=1 and the CNV row drawn.
   views         hideKids on the VIEW that holds the subtrack, with the sibling views
                 hidden by name. Also covers the `_sel` checkbox, since the subtrack is
                 `parent wgEncodeRegDnaseSignal off`, and pins the superTrack side effect
                 below.
   views.xfail   the same thing aimed at the COMPOSITE instead, which loses the row.
                 Expected to fail.
   supertrack    varsInPubs hideKids + one member: `exact: true`, because a test that only
                 checked the member was present would pass with all six drawn.
   urllen        {cCREs: hideKids} must not become the 1701-variable, 42,020-character GET
                 that Apache answered with 414. Checks `noText: "Too Long"`, since a 414
                 renders as a perfectly good page; the derive baseline pins it at 3.
   customtrack   addCustomTrack: with inline BED, tabs and newlines surviving the trip.
   scale         a 3x run draws the same rows as a 1x one.
   ordered       `ordered: true` on rows:, and the fact that a row which was not drawn is
                 reported by rows: alone rather than failing the order check as well.
   ordered.xfail the same two rows named the wrong way round. Expected to fail -- a flag
                 that cannot fail is not a check, it is a second copy of the set test.
   pagechecks    the `expect:` checks that read the PAGE rather than the track image --
                 `url:`/`noUrl:` on the address, `has:`/`noHas:` on a CSS selector -- plus
                 the positional form of `click:` (`{track:, frac:}`), which follows the
                 item box nearest a point. All four exist for bugs that rows:, height: and
                 text: cannot see: a search term's zero-width space stripped out of a URL
                 (#36387), a center label attached to the wrong row (#37785), and an item
                 that cannot be named at all because its track is `type bigBed 3` (#36335).
   pagechecks    the same four aimed the wrong way at once. Expected to fail. The message
     .xfail      names every check that failed, so one run says which of the four broke.
+  colorchecks   `color:`, the one check that reads the track IMAGE: is:/not: on the color
+                a row is mostly drawn in, `part: label` for the center label instead of
+                the items, `at:` for one item rather than the whole row, and the list
+                form. It exists for #36212, where a track that sets both `itemRgb on` and
+                `color` draws its items in the wrong one -- same rows, same height, same
+                names, same tooltips, so nothing but the pixels can tell.
+  colorchecks   the same six aimed wrong, all in ONE expect: step so the message has to
+    .xfail      name all six. Expected to fail. The comment lists them in order; read the
+                log rather than trusting the exit code.
   expectfail    an assertion that is plainly false. Expected to fail -- if it ever passes,
     .xfail      `expect:` has stopped throwing and every other test here means nothing.
   make parity   FAST vs slow, and a rerun, on composite. FAST drops the dwells and the
                 recording and must not change what the page ends up showing; the rerun
                 catches state left behind in the cart.
 
 Two things these tests found
 ----------------------------
 
 Worth knowing before writing more:
 
   * Turning on anything under a superTrack sends `<superTrack>=show`, and every OTHER
     member then comes up at its own trackDb visibility -- so `hide: all` is undone for
     them. views asserts wgEncodeRegMarkH3k27ac comes back, rather than working around it.
     Whether Docent should be cleverer here is an open question, not a settled one.
   * `hideKids` on a VIEW has to enumerate its leaves (a view holds no sub-containers to
     stop at), so views sends 188 variables in a 6,986-character request. That is under
     Apache's 8,190 limit with less room than is comfortable. Its `noText: "Too Long"` is
     what turns a future overflow into a clear failure instead of a strange one.
 
 Still to write
 --------------
 
   mouseover:      by item: on stacked items, and the timing case where a neighbour's
                   tooltip is still up on arrival
   pinShot:        several tooltips in one figure, cursors drawn
   convert:        quickLift onto a GenArk haplotype, hideDefaults re-checked -- note a
                   session taken after it cannot be checked in, see #38046
   drag:           each of then: zoom / highlight / cancel
   addHub:,
   addPublicHub:   the two hub attach paths (a stable hub URL is the hard part)
   montage:        panel order, lettering, a named shot that was never taken
   goShow:         the suggestion menu, including a `pick:` that matches nothing
   loadSession:    the three remote forms -- only the local-file form is covered
   the YAML lint   `{item:name}` with no space warns and drops the argument. This needs a
                   test that reads stderr, which the harness does not do yet.
 
 A test that needs a stable server-side fixture (a hub, a custom track) should carry it
 in the script rather than assume something on disk.
+
+colorchecks is the one exception, and the reason is worth knowing before someone else
+hits it. `color:` has to address a ROW by name, and a custom track cannot be addressed
+by name at all: hgTracks assigns its row id (`ct_<name>_<number>`), which is why
+customtrack asserts on label text instead of on `rows:`. So an inline custom track --
+the self-contained way to get a known color onto the page -- is the one fixture this
+check cannot use. It reads ~/public_html/docentFixtures/itemRgbHub/ instead, which
+tests/regress/rm36212.xfail needs anyway, and which `make preflight` checks is still
+there.