Skip to content

Tae conversion gates - #440

Merged
jjSDET merged 14 commits into
mainfrom
tae-conversion-gates
Aug 19, 2026
Merged

jjSDET merged 14 commits into
mainfrom
tae-conversion-gates

Conversation

@jjSDET

@jjSDET jjSDET commented Aug 13, 2026

Copy link
Copy Markdown
Collaborator

Three gates: explain a failure, map the toolchain, stamp a version

Follow-on to the effverify/effloop fixes. Those were corrections; these are additions aimed at
the same underlying problem — the loop knew things that people had to remember, and 97
documented gotchas only help if someone recalls the right one at the right moment.

Jira: MTE-5827, MTE-5766, MTE-5770

efftriage — map a failed run to the gotcha that explains it (MTE-5827)

HARNESS-GOTCHAS is at 57 entries and CONVERSION-LESSONS ~40. Nobody recalls that mid-debug, so
the same failures get re-derived on device at 1-2 cycles each with the answer already written
down. efftriage reads the run-report.txt/status.json the loop already produced and names the
likely cause, citing the gotcha id and the fix.

Eleven rules (T0-T10), all keyed to patterns from real runs: an absence assertion that can never
come true because the selector matches another surface; an arrival check satisfied behind an
overlay; clickAndSync reporting a landed click as failed; mozEnterText's non-asserting locate;
an ambiguous Compose match reported as not-found; a system overlay masking as absence; a long
press behaving as a tap; a stale PageStateTracker routing through a destructive edge; a
merged-vs-unmerged text miss; an arrival timeout; a crash rather than an assertion failure.

It also encodes the two triage rules people get wrong: scan the FIRST attempt (later attempts die
on leftover state, somewhere later and more confusingly), and treat status.json as authoritative
for pass/fail while the trace explains why.

Read-only — it cannot touch the tree or the device, so it is safe to run on every failure.

effdoctor slice 1 — the toolchain map (MTE-5766)

The eff* tools exist once but are reachable from two checkouts, and effwatch.sh derives its queue
from dirname $0 — which does not resolve symlinks. So the queue it watches depends on the path it
was launched from, while the tools are identical either way. A request dropped in the other
checkout's queue is never consumed and never errors; it just hangs.

effdoctor reads the live process list and prints which queue to actually use, instead of relying on
anyone remembering which checkout was used this time. Also checks: more than one effwatch running
(two gradle builds against one device read as test flakiness), an alt checkout that has diverged
into a separate copy rather than a symlink, effpretty resolvability, a stale
mach_android.lockfile with no process holding it, and an adb device. Exit 1 on anything needing
attention, so it can gate the start of a session. Read-only.

VERSION, CHANGELOG.md, and --version on every tool (MTE-5770)

CalVer (2026.08.13), not semver: these are internal scripts read from a checkout, nobody pins
them, and the only question anyone asks is "is my copy older than the thing being discussed". One
version for tools and docs together, since they cite each other by gotcha/lesson id and a split
version would only create skew. The changelog is curated — changes a consumer depends on, each
citing gotcha ids and Jira keys — because a changelog that becomes a commit log stops being read.

The version lookup deliberately resolves symlinks: read through the other checkout's tools/ dir an
unresolved path finds the wrong repo and reports "unknown" in exactly the situation a staleness
check exists for.

Verification

  • efftriage hardened against 8 labelled failures from the recent search-conversion batch, each with
    a cause established by hand on device. All 8 match.
  • effdoctor run against a live setup caught both real problems present: effwatch launched from the
    non-canonical path, and two tools missing from the alt checkout.
  • All 12 tools print a version, and effnext/effverify/efftriage/effcheck/effloop.sh still
    behave normally.

Reviewer notes

efftriage is advisory and never gates. The fair question is what happens when it is wrong — it
was wrong twice while I hardened it, which is why the labelled corpus exists. One was a
confidently wrong diagnosis (an arrival timeout instead of a selector matching the wrong surface),
which is worse than no diagnosis because it sends you down the wrong path; the other reported a
crash as a footnote so the one mode that also fools effverify printed "no rule matched". Both are
fixed and covered. Being wrong costs a reader one paragraph, not a build.

Expect effdoctor to exit 1 on your machine if your effwatch is launched from
ui-test-modernization — that is the tool working, not a problem with the branch.

No behaviour changes to existing tools beyond --version being handled before other arg
parsing.

Follow-ups, ticketed

MTE-5768 — make the queue drift impossible rather than merely visible: have effwatch resolve
its own path and/or publish a live-queue marker. Behaviour change for anyone deliberately running a
per-checkout queue, so it is proposed with an env override for sign-off.
MTE-5828 — effcheck verb x selector-strategy compatibility matrix.
MTE-5829 — effparity, legacy-vs-port assertion parity, gating "a dropped assertion passes for
the wrong reason".
MTE-5830 — Gate: annotations on every doc entry plus effgates --coverage, making the docs an
index and the tools the enforcement.

Try it

python3 tae-conversion/tools/effdoctor.py                    # start-of-session preflight
python3 tae-conversion/tools/efftriage.py <a failing batch>  # explain a failure

jjSDET added 3 commits August 13, 2026 03:40
HARNESS-GOTCHAS is at 57 entries and CONVERSION-LESSONS ~40. Nobody recalls that mid-debug,
so the same failures get re-derived on device at 1-2 cycles each with the answer already
written down. This reads what the loop already produced and names the likely cause, citing
the gotcha id to go read.

Read-only — it never touches the tree or the device, so it can be run on every failure
without risk. It also encodes the two triage rules people get wrong: scan the FIRST
attempt (later attempts die on leftover state, somewhere later and more confusingly), and
treat status.json as authoritative for pass/fail while the trace explains why.

Eleven rules, all keyed to patterns from real runs: T0 absence assertion that can never
come true (selector matching another surface), T1 arrival check satisfied behind an
overlay, T2 clickAndSync reporting a landed click as failed, T3 mozEnterText's
non-asserting locate, T4 ambiguous Compose match reported as not-found, T5 system overlay
masking as absence, T6 long press behaving as a tap, T7 stale PageStateTracker routing
through a destructive edge, T8 merged-vs-unmerged text miss, T9 arrival timeout, T10 crash
rather than assertion failure.

Hardened against 8 labelled failures from the search batch, each with a cause established
by hand on device: b4/b6-privhist -> A39, b9b -> A40, b13 -> A3/A42, b14d -> A42,
b17b -> A39, b17e -> A22, b10-searchclass -> A37. All 8 now match. Three defects were
found and fixed during that exercise, which is the argument for keeping the corpus:
  - it confidently mis-diagnosed b14d as an arrival timeout, because the absence-assertion
    rule was keyed to the thrown-exception wording rather than the phrasing the trace
    actually logs, and the arrival rule matched the harmless pre-check every navigateToPage
    emits. A confidently wrong triage is worse than none.
  - a crash was reported as a footnote, so the one failure mode that also fools effverify
    printed "no rule matched".
  - it counted one "Started try #1" block per test as a retry, marking every passing class
    run as retried — the exact mistake CONVERSION-LESSONS warns about.
…(MTE-5770)

Tools and docs are versioned together, since they cite each other by gotcha/lesson id and a
split version would only create skew. CalVer rather than semver: these are internal scripts
read from a checkout, nobody pins them, and the only question anyone asks is "is my copy
older than the thing being discussed".

- VERSION at the tae-conversion root, currently 2026.08.13.
- CHANGELOG.md, curated rather than a commit log: entries are changes a consumer depends on
  (a new gate, changed verdict semantics, changed flags), each citing its gotcha ids and Jira
  keys so "when did this gate appear" is answerable.
- --version on all 12 tools (10 python, 2 shell), handled before any other arg parsing.

The version lookup deliberately resolves symlinks. These tools are routinely invoked through
another checkout's tools/ dir, where the eff* entries are symlinks into this repo; an
unresolved path would look for VERSION in the wrong repo and report "unknown" in exactly the
situation a staleness check exists for. That symlink layout is also how MTE-5764 stayed
invisible to us while being broken for everyone else, which is the argument for the stamp.

Verified: all 12 tools print a version, and effnext/effverify/efftriage/effcheck/effloop.sh
still behave normally (effloop's no-arg exit 1 is the pre-existing ${1:?} behaviour).
…E-5766)

The eff* tools exist once but are reachable from two checkouts — testops-tools/tae-conversion/tools
and ui-test-modernization/tools, where the entries are symlinks into the former. effwatch.sh
computes its queue from `dirname $0`, which does NOT resolve symlinks, so the queue it watches
is a function of the path it was launched from while the tools themselves are identical either
way. Drop a request in the other checkout's queue and it sits there forever: nothing consumes
it, nothing errors, and the run simply never happens.

That has bitten us more than once, most recently this batch, so this stops relying on anyone
remembering which checkout was used this time. effdoctor reads the live process list and prints
the queue to actually use.

Slice 1 checks, all read-only:
- the toolchain map: canonical tools + queue, and per live effwatch its pid, launch path and the
  queue it is therefore watching, flagged canonical or not
- more than one effwatch running (two gradle builds on one device read as flakiness, MTE-5768)
- the alt checkout diverging: an eff* entry that is a SEPARATE copy rather than a symlink, or
  missing entirely — which is how a tool silently ages out of one checkout
- effpretty resolvable under $REPO (MTE-5764 / gotcha A24)
- a stale mach_android.lockfile with no process holding it (gotcha A38)
- an adb device present

Exit 1 on anything needing attention, so it can gate the start of a session.

Run against the live setup it immediately reported both real problems: effwatch was launched from
ui-test-modernization so the canonical queue was the wrong place to queue (the exact failure from
earlier today), and effdoctor/efftriage were missing from the alt checkout. The latter is now
symlinked, and resolves to the right VERSION through the symlink, which is what the realpath-based
version lookup exists for.
@jjSDET
jjSDET requested a review from AaronMT August 13, 2026 08:47
@jjSDET jjSDET self-assigned this Aug 13, 2026
@jjSDET jjSDET changed the title Tae conversion gates (WIP) Tae conversion gates Aug 13, 2026
@jjSDET
jjSDET marked this pull request as draft August 13, 2026 09:03
jjSDET added 10 commits August 13, 2026 04:04
… (MTE-5766)

Its first live run misdiagnosed the very thing it exists to prevent. effwatch had been launched as
`./tae-conversion/tools/effwatch.sh`, and the relative path was resolved with os.path.abspath — i.e.
against effdoctor's cwd. Run from the firefox repo, it reported the live queue as
firefox/tae-conversion/conversion-runs/_queue, a directory that has never existed, and advised
queueing into it. A request dropped there is never consumed and never errors, which is the exact
failure mode this tool was written to catch.

Read the watcher's own cwd via lsof and resolve against that, so the answer no longer depends on
where effdoctor was invoked from. Absolute launch paths are used as-is.

Two guards for the class rather than the instance: a computed queue that is not a directory on disk
is a FAIL, and a watcher whose cwd cannot be read is reported as undetermined with the lsof command
to run. Both refuse to name a path instead of naming a wrong one, since a confidently wrong answer
sends the reader somewhere silent.

Verified: correct and identical from two different cwds, and the FAIL and multi-watcher paths
exercised with a decoy watcher whose computed queue does not exist.
…nd (MTE-5766, MTE-5827)

The toolchain had no tests. Its verdicts are what we decide "done" on, and the way we checked them
was to run a tool, read its output, and judge whether it looked right — which is how two confidently
wrong diagnoses survived review. Both were in the gates written to prevent exactly that.

44 tests, stdlib unittest, no device or Firefox checkout needed: ps/lsof/adb/git are stubbed and the
edge cases build synthetic batch dirs. The efftriage rule tests instead run against REAL labelled
conversion runs, now checked in under tests/fixtures/corpus with a labels.json manifest. That
distinction is the point: hand-written traces only exercise the shapes their author imagined, so they
go green while the tool mismatches real output — a false green in the suite meant to prevent false
greens. The corpus also existed only as batch dirs in one person's conversion-runs/, unversioned and
impossible to run in CI, which is a poor home for the campaign's main diagnostic asset.

Four defects, three of them in code already reviewed by eye and pronounced fine:

- efftriage diagnosed GREEN runs. Traces normally carry non-fatal [ERR] lines (nav-graph polling,
  tolerated absence checks) and the rules scanned them whatever the outcome, so a passing run was
  reported as an absence-assertion failure. Two labelled runs in our own corpus were affected. Still
  triaged on purpose: a retry-pass, where attempt 1 did fail and clean=false means flaky-not-done,
  and a crash, since crash mode is where `outcome: pass` is itself the lie (MTE-5822).
- efftriage blamed the harness for a typo: a nonexistent batch dir came back as gotcha A24, sending
  the reader to check effpretty resolution and pull dumps off a device. It also tracebacked.
- effdoctor named the wrong queue — the failure it exists to prevent. effwatch is launched by a
  relative path, resolved with abspath against effdoctor's cwd instead of the watcher's, so from a
  Firefox checkout it reported a queue under firefox/tae-conversion/ that has never existed and told
  us to use it. Requests there are never consumed and never error. Now reads the watcher's own cwd
  (/proc, then lsof); a computed queue that is not on disk is a FAIL and an unreadable cwd is
  "undetermined", because declining to answer beats answering wrongly.
- effdoctor's self-exclusion skipped any ps line containing the substring "effdoctor", so a watcher
  under a directory with that name was invisible. Unnoticed watchers are the whole point of the tool.

Recorded as known gaps rather than papered over: rules T3/T4/T5 have no labelled example in the
53-run corpus, so they are unvalidated rather than known-good, and 3 of 28 failures still get no
diagnosis. Both are asserted, so the lists cannot go stale in either direction.
… MTE-5766)

Baselining a legacy test for batch 2 produced outcome=pass, failures=0 — for a test that never ran.
MainMenuTest.verifyTheMoreMainMenuSummarizePageButtonTest is @ignore'd on bug 2059592, so it skipped,
and effloop's verdict was `failures == 0 and tests > 0`, which a skip satisfies. efftriage then read
that outcome and said "run passed — nothing to triage". effverify was the only tool that got it right
(ok:false, passed:false), so the disagreement between them was the tell.

A skip asserts nothing, so it must never read as green:

- effloop now counts what actually ran: outcome=skipped when nothing did, partial when only some did,
  both exiting 5 — a new code meaning "ran, but the verdict is incomplete", distinct from 1 (failed)
  because the test did not fail, it never started.
- efftriage rule T11 diagnoses it from the per-test statuses rather than from `outcome`, since the
  field that should have raised the alarm was the one lying. It names the gate candidates: an
  Assume()/assumeTrue, an @ignore, a Nimbus flag, a pref, absent hardware.
- Documented as gotcha A44, with the standing instruction not to read failures:0 as success on its
  own but to confirm results[].status == "pass" for every test expected to run.

Run over the existing corpus, T11 immediately found 2 silently SKIPPED tests (browserSearchBarItemsTest,
homeSearchBarItemsTest) inside an earlier 10-test run that nothing had ever reported — that run was
recorded as a plain failure of one other test. The labelled fixture keeps both shapes: the skip scored
as pass, and the mixed run.

Verified end-to-end on device, not just in unit tests: re-running the same @ignore'd test now yields
outcome=skipped with effloop_exit=5. Suite is 48 tests, green with ResourceWarnings as errors.
…ck (T12, T13, A45)

Both found while converting the first batch-2 test, and both cases where the tool sent the reader
somewhere unhelpful.

T12 — a compile failure was answered with "no rule matched". status.json states compiled:false
outright, so there is never a reason to make someone hunt through a trace that was never produced.
It now names build-report.txt, where effloop already puts the Kotlin errors with file:line. This also
re-labels an existing fixture: b18-fxsuggest was recorded as an "empty report" case and drew the
misleading A24 advice (check effpretty resolution, pull dumps off the device) when it had simply
failed to compile.

T13 — the arrival check passed on the WRONG screen. Both requiredForPage anchors for a new Settings
sub-page also resolved on the parent Settings screen (the row carrying the destination's own title,
plus the ubiquitous Navigate up), so mozIsOnPageNow() reported arrival and navigateToPage skipped its
own click. Documented as gotcha A45, alongside A42 and the nav-graph gap it rhymes with.

T5 was tightened in the same pass. Its matcher keyed on "non-application window(s) present", but the
status bar is itself a SYSTEM window, so that warning is in nearly every dump and the rule fired on
almost any not-found. It answered the A45 case above with "a system overlay was over the app",
blaming the status bar — its first real firing was a false positive, which is exactly why it was
listed as unvalidated rather than known-good. It now requires that the app NOT hold focus.

T13's first draft keyed on "not found" and stole the diagnosis from six other rules; the corpus tests
caught that immediately. It is now narrowed to the group-level failure line and placed last so
anything more specific wins first — a reminder that a new rule's blast radius needs checking against
the whole corpus, not just the case that motivated it.
…ged row

Cost a cycle on the never-translate conversion, and it is the reason legacy tests fall back to
positional UiSelector().index(n) for checked state: in the unmerged a11y tree the stateful row has no
text, content description or resource-id. Two traps stack — the Switch is blanked with
clearAndSetSemantics so no ToggleableState exists at all (assert selected, not checked), and the state
lives on the merging row, so COMPOSE_BY_TEXT resolves a stateless descendant.

Also records that a surviving testTag is not evidence of assertable state: RadioButtonListItem tags the
icon before the clearAndSetSemantics that strips selected/role/onClick.
…A46, MTE-5827)

The never-translate conversion's real failure — mozVerifyElementIsChecked against a node that carries
no state — came back as "no rule matched". It now names gotcha A46 and both halves of the fix: assert
`selected` rather than `checked`, and match on the MERGED tree. This is the shape behind every legacy
test that asserts checked state via positional UiSelector().index(n), so it will come up again.

The first draft over-matched. Keying on "' is (not )?checked" also hit the `[CMD] Verifying ... is
checked...` and `[OK] ... is checked` lines of runs that PASSED that assertion and failed for unrelated
reasons, and it silently stole the diagnosis from two labelled gap cases (a keyboard-close
PerformException and the ads-client NoHandle crash). The corpus tripwire caught it immediately — the
second time in this batch that a new rule's blast radius was wider than the case that motivated it, so
the matcher now requires the [ERR] marker.

Fixture keeps the run as labelled evidence, including the detail that it predates the ScreenDump those
verbs now emit, which is what made it opaque in the first place.
Both were consumer-facing and stale: 11 rules -> 15 (T0-T14), 44 tests -> 48. Also notes that only 12
of the 15 are trace-matching entries in RULES, since T10/T11/T12 are status-based checks in triage() and
anyone counting RULES would otherwise think three were missing.
…TE-5763)

I filed a conversion bug with a TestRail id I had transcribed wrong. Comment 0 cannot be edited through
the Bugzilla API, and update only whitelisted summary/status/keywords/whiteboard/priority/severity plus
relations and assignee — so there was no way to annotate the bug at all, and the error would have stood
in a public bug indefinitely.

On create, `comment` remains the description; on update it is now a new comment. Blank is ignored rather
than posting an empty comment, and still counts as "nothing to change".

Adds effbug_tests.py, which stubs the one function that talks to Bugzilla and asserts what would be
sent. Worth pinning precisely because a wrong field writes to a public bug and there is no undo: it also
covers the existing rule that relations are wrapped as {"add": [...]} rather than sent as a bare list,
which would REPLACE the set and silently drop every other bug a meta bug tracks.
… not resolve (MTE-5827)

EffScreenDumpRunner already exists in ui/efficiency/devtools and does exactly what is needed when a
selector fails — an on-demand dump of a screen's live Compose tree, so selectors are authored against
ground truth rather than inferred from legacy robots whose locators are often stale. It is easy to miss.
Not knowing it was there, I spent several device cycles provoking failures with throwaway assertions
purely to obtain a dump.

efftriage now surfaces it, with the queue invocation, whenever the report contains a "not found", plus
the caveat from its own docstring: graph-navigable pages only, since a launch-reached page like
customTabs has no nav edge.

This is a discoverability fix rather than a new capability, which is the cheaper half of most tooling
problems here.
…ever fire, and record A47-A53

Hardened live against the batch-2 conversions (MTE-5827, MTE-5831). Every change here came from a real failure
during those conversions, and each new rule has a labelled corpus fixture taken from the run that motivated it.

RULE DEFECTS FIXED. All three looked correct and could never fire, which is worse than a missing rule because
"no rule matched" at least sends you to read the report:

* T4 claimed to detect an AMBIGUOUS Compose match but its regex matched Compose's ZERO-match messages
  ("Expected exactly '1' node but could not find any node...", "Can't retrieve node at index '0'"). Those
  appear in T1-arrival-timeout, T7-multi and T2-selector-surface, so had T4 ever been ordered earlier it would
  have confidently misdiagnosed three labelled runs. Now requires "but found 'N' nodes" with N > 1.
* T14 matched only "is (not) checked", so following the advice in its own gotcha (A46: switch to
  mozVerifyElementIsSelected) produced "is not selected" -- a message the rule could not see. The gotcha
  silently stopped applying the moment you obeyed it. Now matches both state words, and its fix text covers
  what to do when the failure is already "is not selected".
* T9's first branch expected "Navigation to 'X' failed", but the ordinary arrival failure throws "Failed to
  navigate to X", which only ever lands in the FAILURES summary and never inside an [ERR] window. The branch
  was dead. Removed rather than extended, and the case is now handled by T18 in the report tier.

NEW REPORT-LEVEL TIER. Rules in REPORT_RULES match against status.json's primary failure message instead of a
window of trace around an [ERR] line. This exists because some failures leave NO [ERR] line at all -- a crashed
instrumentation process produces an ~11-line report -- and others put the real exception outside the window
(failure_windows takes 25 lines after the [ERR]; the ads-client stack lands 36 lines later). Widening the
window was rejected: it drags stack text into unrelated windows, which is the same cross-contamination that
makes T6 and T9 fire on the wrong evidence today (filed as MTE-5832).

* T16 / A37 - the instrumentation process crashed; there is no assertion to read.
* T17 / K13 - a UniFFI fake built with NoHandle had an un-overridden method called. Recurs in 3 of 19 corpus
  runs and can fail a test AFTER its assertions have passed. Deliberately NOT matched report-wide: the same
  text appears as a Suppressed exception in T0-absence-assert and T9-permission, so a substring match would
  steal both.
* T18 / A6+A45+B7 - the thrown form of an arrival failure, pointing at the anchor's STRATEGY first (an
  ESPRESSO selector cannot see a Compose node, and Settings screens are routinely a View toolbar over Compose
  content).

TEST GUARD FIXED. efftriage_tests.rule_ids() only inspected RULES, so the three new report-tier rules were
admitted with no labelled example -- exactly what that guard exists to prevent. It now covers both tiers. Three
fixtures added from real runs: T14-selected-not-checked, T15-external-app-blocked, T18-arrival-wrong-tree.
Corpus 19 -> 21 labelled runs; two long-standing known_gap runs are now diagnosed. Suite: 58 tests, green.

DOCS. HARNESS-GOTCHAS gains a "How to write an entry" convention, which is the most important change here.
Claims about the HARNESS generalise and may be stated plainly; claims about PRODUCT code must be scoped to the
file they were observed in, must say what to verify first, and should carry a counter-example. A46 asserted
that Compose toggle rows expose state as `selected`; that is true of ListItem.kt and false of
SearchEngineShortcuts.kt, and reading it as a law cost two cycles. A46 is rescoped accordingly, with its
counter-example named.

New entries: A47 (requiresScroll is inert for the singular mozVerify, and mozSwipeTo's direction default is
the opposite of the precondition path's), A48 (an external-app assertion blocked by a system dialog, and the
legacy twin that swallows its AssertionFailedError), A49 (a rule deliberately NOT mechanised, with the reason
recorded so it does not read as an oversight), A50 (the stylus prompt steals keystrokes and causes no locate
miss, so OverlayRegistry never fires), A51 (mozEnterText can leave a Compose field looking filled while the app
never saw it), A52 (a custom search engine survives a retry but MockWebServer's port does not), A53 (some
suggestion rows sit outside the Compose test tree -- read the right block of the dump).

Also: efftriage's header no longer hard-codes a gotcha count. It said "57" and was wrong twice, which is the
same failure mode the rules are meant to prevent. It now states the two standing rules instead -- match only
text OBSERVED in a real run, and prefer "no rule matched" to a confident mismatch.
@jjSDET jjSDET changed the title (WIP) Tae conversion gates Tae conversion gates Aug 13, 2026
@jjSDET
jjSDET marked this pull request as ready for review August 13, 2026 14:48
…g, gotchas A54-A59

Comes out of converting the seven site-permissions smoke tests (bugs 2064810/2064815/2064823/2064833).

efftriage gains rule T19 for "No navigation path found from X to Y", which it previously left unmatched
while its generic hint pointed at selectors and dumps - misleading, because that failure is a navigation
graph gap where nothing was searched for on screen. Labelled corpus fixture T19-no-nav-path is a real
run, so the rule is validated rather than listed as unvalidated.

effnext now warns when a pick is already converted on another local branch. Its in-tree filter only sees
the checked-out branch, so it offered CustomTabsTest.verifyLoginSaveInCustomTabTest, which has been
converted and awaiting review on efficiency-batch-2 since last week. Advisory rather than a filter, and
backup/* is excluded, because branches also carry abandoned work - the two faker conversions - and
filtering on any branch would silently drop outstanding tests from the pool.

That check shipped broken twice while being written, both times reporting a clean "nothing on other
branches" while checking nothing at all: git grep -E cannot parse \b or \s, and subprocess was never
imported so the NameError was swallowed by a bare except. Both paths now surface as branches_unchecked,
and tests/effnext_tests.py covers them.

HARNESS-GOTCHAS A54-A59 record what the device runs taught: option text is label plus subtext joined by a
newline; permission-screen res-ids are shared across screens and ESPRESSO_BY_ID ignores visibility;
BrowserPage has inbound edges only from HomePage and itself; a page-content text selector can resolve a
non-clickable heading, giving "found" then a failed click; a system app-permission row title is present
whether the permission is allowed or denied, so assert checkSelfPermission instead; and a page object
missing from PageContext never registers its navigation.

docs/SMOKE-CONVERSION-AUDIT.md is new: the standing burndown of remaining legacy @smoketest methods, what
blocks each, and the parser to reproduce the count.
@jjSDET
jjSDET merged commit 3d875d2 into main Aug 19, 2026
4 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants