Run the end-to-end loop for landing a legacy Fenix UI test converted onto the ui/efficiency framework: convert → file a Bugzilla bug → commit with the real bug number → track in Jira (conversion vs. enablement) → open a moz-phab review. Use this when a conversion is written and needs to become a filed bug, a properly-numbered commit, Jira tracking, and a submitted revision — i.e. the "paperwork + submit" workflow around a ui/efficiency conversion, not the test authoring itself (that's the efficiency-test-authoring skill). Also covers keeping the tracker/dashboards in sync after landing.
68
81%
Does it follow best practices?
Run evals on this skill
Adds up to 20 points to the overall score
View guide
Passed
No findings from the security scan
This skill drives the workflow around a conversion once the test itself is written. Test authoring
is a separate skill (efficiency-test-authoring). The five steps:
convert → file bug (blocking meta 2030727) → annotate @Converted + commit (with bug #) → track in Jira
→ submit for review
tools/jiratool.py (preferred — it works headless), falling back to the Atlassian
connector only if that tool is unavailable. It cannot reach Bugzilla or Phabricator directly.effwatch) runs the git + Bugzilla actions on the engineer's machine and returns
results. It never pushes and never submits.effwatch, keeps a device attached, and runs the final moz-phab submit.The eff* tools live in the testops-tools repo under tae-conversion/tools/ (see
tae-conversion/README.md for setup). Clone that repo and start effwatch before using this loop.
effwatch running on your machine (from testops-tools/tae-conversion/tools/), device/emulator attached.~/.zshenv (export BUGZILLA_API_KEY=…) or a
tae-conversion/tools/.eff.env file (gitignored). Never paste it into chat.moz-phab installed and authenticated to phabricator.services.mozilla.com.This skill is written for the Fenix smoke-conversion campaign. If you're running it elsewhere, these are the only values you need to change:
| Value | Current | What it's for |
|---|---|---|
| Template bug | 2057054 | Cloned for product/component/version → Firefox for Android :: UI Tests |
| Tracking meta bug | 2030727 | Every conversion bug must block it ("blocks": [2030727]) |
| Reviewers | isabel_rios, aaronmt | Default Phabricator reviewers (EFF_REVIEWERS) |
| Conversion parent | MTE-5731 | Story that conversion sub-tasks hang off |
| Enablement parent | MTE-5715 (or MTE-5688) | Harness-hardening / tech-debt story |
| Jira cloud | mozilla-hub.atlassian.net | Atlassian connector target |
Convert the legacy test onto ui/efficiency (see efficiency-test-authoring). Run effcheck.py (static
pre-flight) then build/run via the bridge (effverify / effwatch) until green, or "good enough + notes."
Before you file anything: run the parity audit from the tae-test-review skill. A conversion that
dropped a legacy assertion still goes green, so a passing run is not the gate — the legacy-vs-port diff is.
Land-blocking findings are cheaper to fix now than after the bug and commit exist.
effbug)Drop conversion-runs/_queue/<id>.request.json:
{ "bug": "create",
"summary": "[efficiency] Convert <Test>.<method> to ui/efficiency",
"comment": "<what was ported; parity notes>",
"why": "<one-line rationale>",
"kind": "conversion", // conversion | enablement | tooling — picks the description footer
"testrail": "<id(s)>",
"template_bug": "2057054", // clones product/component/version → Firefox for Android :: UI Tests
"blocks": [2030727], // REQUIRED for conversions — the tracking meta bug (see below)
"type": "task" }effbug files the bug, then rewords the title to Bug NNNNN - <summary> so it matches the commit
subject exactly, and self-assigns to the API-key owner. It returns the number in
_bug/<id>.bug-result.json.
Check the TestRail ids before you file. Comment 0 cannot be edited through the BMO API, so a wrong id is permanent unless you post a correction. Compare each id against the line immediately above the legacy method (not a grep context window — a neighbouring test's id looks identical in kind); three of six were wrong in one sitting this way, and two filed bugs needed correcting comments. If you do need to correct one after filing:
{ "bug": "update", "ids": [2064815], "comment": "Correction to comment 0: …" }Hang the bug off the tracking meta bug — "blocks": [2030727].
Bug 2030727 is [meta] TAE - Migrate and remove legacy tests; it tracks the campaign via its depends_on list, so each conversion bug must block it.
Pass blocks at create time — that is one field, versus a second round-trip afterwards, and a conversion
that never gets linked is invisible to whoever reads the meta bug for campaign status.
Scope: test-conversion bugs go on the meta; tooling/docs/harness bugs do not. The meta is specifically about migrating and removing legacy tests, which is why e.g. the effview-tool and harness-docs bugs are deliberately absent from it. If a conversion also needed harness work, the conversion bug still blocks the meta — the enablement is tracked in Jira (step 4), not by a second meta entry.
To backfill one you already filed:
{ "bug": "update", "ids": [NNNNN], "blocks": [2030727] }To close one as a duplicate — which happens when the same test gets converted twice, see step 1 —
{ "bug": "update", "ids": [NNNNN], "dupe_of": MMMMM, "self_assign": false }. effbug fills in
RESOLVED/DUPLICATE for you; passing resolution alone is rejected, because Bugzilla will not take
DUPLICATE without dupe_of. Note there is no API for editing a bug's description: a wrong comment 0
can only be fixed by a human in the web UI, so get the mechanism right before you file.
update wraps relation lists as {"add": [...]} so this appends. Never PUT a bare list to a meta bug's
depends_on — Bugzilla treats that as replace and it would drop every other bug the meta tracks.
effgit)First annotate the legacy method(s) you just replaced — this goes in the SAME commit as the conversion, and it is the step most often forgotten:
@Converted(
replacedBy = ["org.mozilla.fenix.ui.efficiency.tests.<Class>#<method>"],
bug = NNNNN, // the bug you filed in step 2
since = "YYYY-MM",
notes = "Legacy also asserted X; not carried over because …", // only if coverage was dropped
)The gate is green locally (step 1's effverify verdict), not landed — you cannot annotate after
landing without a second bug and a second review, and every conversion in this campaign has landed the
annotation alongside its replacement. replacedBy is required, one entry per replacement, and each must
resolve to a real non-@Ignored @Test. Put the parity gaps from step 1 in notes — that is the
auditable record of what did not carry over. Annotate the legacy method in place; do not delete it, it
keeps running alongside the replacement.
Then write the commit message to conversion-runs/<batch>/msg.txt with
Bug NNNNN - [efficiency] … r=isabel_rios,aaronmt and drop
{ "git":"commit", "message_file":"<batch>/msg.txt", "paths":[...] } — the paths list must include the
legacy test file you just annotated as well as the new/changed efficiency files. (If a commit already
exists with a placeholder, backfill by rewording — the loop files the bug before committing going
forward, so no reword is needed. effgit's amend does not stage: send stage first, then amend.)
jiratool.py)Separate strict conversion from the enablement it sometimes forces, so conversion effort can be measured:
conversion under the Smoke-conversion campaign story MTE-5731.enablement under
Harness Hardening MTE-5715 (or Tech-Debt MTE-5688), linked ("Relates") to the conversion sub-task.Use tools/jiratool.py (works headless; no Atlassian connector needed). Bodies come from a file, so write
the description to a temp file first:
python3 jiratool.py create '<summary>' --file body.txt --parent MTE-5731 --issuetype Sub-task --label conversion
python3 jiratool.py create '<summary>' --file body.txt --parent MTE-5715 --issuetype Sub-task --label enablement
python3 jiratool.py link <enablement-key> Relates <conversion-key>
python3 jiratool.py assign <key> --me # create does NOT self-assign; do this explicitlycreate defaults to --issuetype Story and no labels, so pass both every time or the item lands as an
unlabelled Story in the wrong shape. --label is repeatable. If the Atlassian MCP connector happens to be
connected it also works, but do not count on it — it is absent in headless/cron runs.
An enablement sub-task is warranted whenever the conversion needed a new page object, selector twin, nav
edge or moz*/mozVerify* primitive — i.e. build mode 3. Write up what the gap was and what the proper
fix would be, not just the workaround you shipped.
Submitting/landing stays with the engineer. Mozilla's moz-phab has no --dry-run — it's interactive: it
prints the commit list and prompts Y/n before creating anything (that's your preview). Bound the range so it
can't touch already-landed base commits:
moz-phab submit --reviewer isabel_rios --reviewer aaronmt <first-new-commit>(or python3 tae-conversion/tools/effsubmit.py --start <first-new-commit> --execute).
A cherry-picked base whose revision is CLOSED blocks the whole submit. Borrowing an unlanded commit as a base is
fine for building and running, but moz-phab refuses the stack if any commit in range maps to a closed revision, and
unpicking it late means reworking whatever depended on it. So before cherry-picking, diff what you actually need
against main: on 2026-08-19 everything needed was already landed except a one-line helper written in the same
session, so the borrowed commit was dropped and the helper inlined. And never create a file that the unlanded commit
also creates — that is an add/add conflict on every rebase until it lands. Then add the
testing-exception-unchanged tag in the Phabricator web UI (no moz-phab CLI flag exists for it). moz-phab
keys off Differential Revision: trailers, so base commits that carry them are excluded automatically — even
if they landed on autoland and aren't in your local central yet.
Dropping a commit does not remove its revision from the stack graph. Abandoning the revision leaves the next one still recording it as a parent, with a diff based on the commit you dropped, so the stack renders with an abandoned revision wedged in the middle. Resubmitting the whole range is what re-parents it — submitting only the commits whose content changed leaves the stale edge in place.
Two consequences to warn the engineer about before they push: every revision gets a fresh diff, because a rebase changes every hash, and revisions that were already accepted reset to needs-review. If avoiding that churn matters more than a wording fix, leave the commit messages alone — amending one forces the upload you were trying to avoid.
Re-sync the tracker so conversion counts catch up with the @Converted markers that landed in step 3 (see
tae-conversion/README.md → "Reconciling the ledger" and tae-conversion/tools/reconcile_conversion.py).
If reconcile reports a converted test with no @Converted marker, the annotation was missed in step 3 —
that is a gap to backfill under a follow-up bug, not the normal path. Annotating is step 3's job.
6821231
If you maintain this skill, you can claim it as your own. Once claimed, you can manage eval scenarios, bundle related skills, attach documentation or rules, and ensure cross-agent compatibility.