Usage Guide¶
UFT Phoenix converts OpenText UFT One (formerly Micro Focus UFT / QTP) VBScript GUI tests to UFT Python. There are exactly two conversion paths, with nothing in between:
- Local filesystem — convert UFT test directories stored on disk (local folders or UNC shares).
- Whole ALM project — convert an entire ALM domain/project, all or
nothing. There is no folder- or count-scoped ALM mode; the scope is reduced
only by explicitly parking tests out of it — by id (
--alm-exclude-test-id) or by glob against their ALM folder path, name or id (--include/--exclude).
ALM conversion is whole-project because a UFT test that calls another test's shareable ("reusable") action forms a call chain that must run under one script engine: a callee action executes inside the caller's engine, so a mixed VBScript/Python chain fails at runtime. Converting only part of a project would both leave mixed-engine chains behind and miss inbound references from tests outside the subset. Converting the whole project keeps every chain uniform Python. Cross-test shareable actions are preserved by native reference reconstruction (see below), not by copying.
Most users drive Phoenix through the Migration Console (uft-migrate-gui,
documented field-by-field in gui_field_guide.md); this
guide covers the uft-migrate command line the console builds on.
scan/analyze, convert, run, validate and summarize print the project
root at start (PROJECT_ROOT: — the folder the command was launched from) and
write their artifacts to out\<run id> under it. --output-root does not
move the run folder: on convert/run it relocates the converted output
(default out\<run id>\converted), and on the other commands a scratch folder
(default out\<run id>\artifacts). Launch from a folder you can write to —
never C:\Windows\System32, and never the install folder, which an upgrade or
an uninstall clears.
With --run-id omitted, a filesystem run gets a UTC timestamp id
(YYYYMMDD_HHMMSS), while an ALM run is keyed on <Domain>-<Project> from the
--domain/--project you passed — so repeat ALM runs re-enter the same folder,
with the resume and anti-clobber machinery that implies, unless --run-id is
given. The name is normalised: a domain written in capitals (letters, digits and
underscores only) is title-cased, and any character other than a letter, digit,
-, _ or . becomes an underscore, so the domain MY_DOMAIN and project
MY_PROJECT run in out\My_Domain-MY_PROJECT. doctor and restore sit
outside this entirely: both are dispatched before the banner and the run-folder
creation, so neither prints PROJECT_ROOT: nor creates a run folder.
summarize is the exception among those: it consumes an existing run's
reports and links them with relative paths, so with no --run-id it writes the
executive summary into the most recent run folder rather than creating one
of its own (only when there is no previous run does it fall back to a new
timestamped folder). One guard governs that default: when the most recent run
folder already holds an executive_summary.json, .html or .md, bare
summarize writes nothing — it prints "status": "refused" and exits 2 rather
than overwrite an approval document the run itself wrote from evidence
summarize cannot rebuild. Only run (filesystem and --alm) writes an
executive summary, so bare summarize after any run is refused; the default
above is what applies to scan, convert and ALM-analysis folders. Passing
--run-id bypasses the guard: summarize --run-id <that run> overwrites the
approval document the run wrote with one rebuilt from the report JSON alone. To
keep the original, pass a new --run-id together with
--scan-json/--convert-json/--validation-json pointing at the run's
reports — the rebuilt summary's detail-report links are relative and will not
open from the new folder, so read the details in the original run folder.
Commands¶
| Command | Purpose |
|---|---|
doctor |
Preflight the environment: Python, pywin32, UFT install, IronPython runtime completeness, ALM client, output write access, optional live ALM login. Run this first on every new machine. |
scan / analyze |
Discover tests, inventory blockers/warnings per file, build the dependency graph, produce readiness reports. No changes are made. |
convert |
Convert tests (filesystem or ALM). ALM mode: download → convert → AOM build → optional upload/verify → optional Test Lab run. |
run |
Closed loop in one pass. Filesystem: scan → convert → validate → summarize. With --alm: the same whole-project ALM conversion convert --alm performs, plus the executive summary (ready_for_approval, alm_status). This is the command the Migration Console's Run Conversion button issues. |
validate |
Post-conversion checks (Python syntax, UFT method-call rules) on a converted tree. |
summarize |
Build the executive summary from existing report JSONs. |
restore |
Restore ALM tests to their original pre-conversion payloads from a prior run's frozen snapshots (out\<RUN_ID>\alm_aom_work\alm_rollback\test_<id>_source). out\<RUN_ID>\alm_aom_work\test_<id>_source is read only as a legacy fallback — it is the live staging tree, which convert re-downloads on every attempt, so after a second run it holds the converted payload. A snapshot that already contains Python (Script.pts with no Script.mts) is refused rather than restored. Snapshots are frozen by any run that converts a downloaded test for real — real conversions, and also deep ALM analysis (--analysis-depth deep), which AOM-builds every test. A standard analysis and an --upload --dry-run gate rehearsal freeze nothing, so those run folders leave nothing for --from-run to use. |
Common options (scan, analyze, convert, run, validate, summarize)¶
--config (config file: JSON; YAML only in a source install) ·
--source-root (repeatable) ·
--output-root · --uft-object-model · --custom-uft-method (repeatable) ·
--include / --exclude (glob patterns, repeatable) ·
--exclude-test-path (filesystem conversion-scope exclusion, repeatable) ·
--strictness strict|standard|permissive (--strict is shorthand) ·
--report-format json|md|html|both · --verbose · --max-errors ·
--threads · --emit-dependency-graph ·
--run-id · --resume · --failed-only
--report-format both emits JSON, Markdown, and HTML artifacts.
--max-errors is inert: it is accepted for compatibility and stops nothing.
If --config is not given, Phoenix loads uft-migrate.json (or
uft-migrate.yaml / uft-migrate.yml) from the folder the command was launched
from, when one exists, and the run does not say which file it loaded. The
installed product reads JSON only: a .yaml/.yml file stops the run with
"YAML config requested but PyYAML is not installed", because the offline
package does not include PyYAML. The Migration Console's current directory is
the folder it was launched from — for the Start Menu shortcut, your user
profile folder. Rename or remove a stray file there to get the defaults.
--resume continues an interrupted run with the same run id and otherwise
the same command line. It is accepted only while that run's checkpoint is still
running; after a run that failed or was aborted it is refused with exit 2, and
the fix is to re-run with the same run id rather than to add the flag. The
Migration Console has no --resume control of its own. When the previous run in
that folder is still interrupted it offers a Resume Previous Run dialog, and
passes the flag for you only if you answer Yes.
--resume does not control what a whole-project ALM conversion skips. Every
upload conversion journals each asset to resume\alm_test_results.ndjson in the
run folder. A later conversion in the same run folder skips an asset when the
journal records it uploaded and verified and the server copy is still
Python; the asset is reported ok, flagged resumed, and listed under
Carried Forward. No flag turns this on. Because ALM runs default to the
<Domain>-<Project> folder, a repeat conversion of the same project resumes
unless you pass a new --run-id and run the analysis under that id first. ALM
analysis never resumes and never skips an asset.
A carried-forward test still serves as a callee: its journal record holds the converted action layout that its callers are retargeted against, so a test that calls it is converted as usual in the same run. The one exception is a run folder whose journal an earlier version of Phoenix wrote, before that layout was recorded. A caller of a test carried forward from such a journal is refused with "that callee has no conversion record in this run"; see troubleshooting.md for the fix.
--failed-only re-processes only the assets that failed previously and
merges the result into the previous run's, so the report still covers the
whole scope. It applies to analysis (scan, analyze, and the ALM analysis
convert --alm --dry-run); whole-project ALM conversion is all-or-nothing
and rejects it.
doctor and restore do not share these options — each defines its own flag
set (see the doctor examples below and restore --help). The only overlap is
--output-root, which both declare separately; everything else above, including
--config, --report-format, --verbose and --run-id, is rejected by
argparse on those two commands.
Reading the result (verdict vs. exit code)¶
Every migration command (scan, analyze, convert, run, validate,
summarize, restore) prints a JSON payload on stdout. Three different signals
live in it, they answer different questions, and only run and summarize
publish all three:
| Field | Question it answers | Where it appears |
|---|---|---|
go_no_go |
Is this scope fit to convert? no-go whenever there are scan blockers, unresolved dependency edges or dependency cycles, whenever nothing was assessed (nothing_assessed: true — no tests or libraries were discovered), and, on the ALM path, whenever any in-scope asset did not come back ok. Identical to the verdict in scan_report.json / scan_report.html and to the badge the Migration Console shows. |
Published by scan, analyze, convert and run, including the ALM analysis (convert --alm --dry-run) and ALM conversion, and by summarize whenever the reports it read carry a verdict (absent when they do not — treat a missing verdict as no-go). Not published by validate, which assesses an already-converted tree rather than a scope. |
status / summary.overall_status |
Did the run clear the selected strictness gate? Drives the exit code (0 ok, 2 failed). |
Every migration command. |
ready_for_approval |
Can a human sign this conversion off? The same boolean behind the executive summary's "Ready to approve conversion program" / "Approval pending remediation" badge. It is a strictly wider hold than the exit code: it also goes false for waived converter blockers and for a scan-only run folder with nothing converted. |
run and summarize (both --alm and filesystem run folders) — the two commands that write the executive summary. Equivalent to approval.ready_for_approval in executive_summary.json. |
On the ALM path --strictness is ignored — the ALM converter always runs
strict. convert --alm, the --dry-run analysis included, prints the ALM
pipeline's own status word — ok, completed-with-errors or no-tests, and,
on any run that is not the read-only analysis (the --upload --dry-run gate
rehearsal and the real upload), preflight-failed — and exits 2 unless
every discovered asset result is ok. Out-of-scope assets are not failures
for the verdict but they are non-ok results, so an analysis whose only
non-ok asset is a skipped API test exits 2 with go_no_go: "go" — where
run --alm over the same scope exits 0. Gate on go_no_go.
go_no_go, status and ready_for_approval are not the same thing, and
they diverge in both directions:
"status": "ok", exit 0, verdictno-go.--strictness standard(the default) deliberately tolerates scan blockers, and no strictness level folds in dependency-graph health. A scan carrying 3 blockers exits 0 while its ownscan_report.htmlrenders the red "Hold conversion approval" badge.- Exit 2, verdict
go. Under--strictness stricta review-required finding fails the gate even when nothing blocks conversion — an estate whose only findings areGoTolines exits 2 withgo_no_go: "go"andsummary.gate_failures: ["3 scan review-required finding(s) remain."]. "status": "ok", exit 0, verdictgo,ready_for_approval: false.standard(the default) waives converter blockers: the flagged construct is dropped from the published Python and the run continues. That is a strictness decision, so it does not fail the gate and does not move the scan verdict — but it is not something a human should sign off unread. It is counted insummary.conversion_waived_blockers.summarize: exit 2 on an ALM run folder, exit 0 on a filesystem one, sameno-go. An ALM analysis summary folds its verdict intooverall_status(any non-goverdict holds approval at every strictness), while a filesystem summary'sstatusstays the pure strictness gate — sosummarizeover a filesystem run whose scan saidno-gostill prints"status": "ok"and exits 0. Gate ongo_no_go/ready_for_approval, never onsummarize's exit code.
A test that came back "no actions" / "no convertible actions" is not one of
these tolerated cases. It is a real UFT test that produced no converted output
and is still VBScript, so it fails the gate at every strictness: status
failed, exit 2. On the filesystem path the gate failure reads "N test(s) were
skipped without producing converted output." and the count is
summary.conversion_unconverted_skips; on the ALM path the asset carries a
non-ok status and appears in the conversion status breakdown.
So exit 0 does not mean go, exit 2 does not mean no-go, and neither one
means approvable. Gate conversion approval on go_no_go, treat the exit code
as the strictness gate it is, and gate the human sign-off of a run on
ready_for_approval. Read the verdicts from the run's report files rather than
piping stdout to jq — stdout starts with a PROJECT_ROOT: banner line, and
with --verbose an ALM run (convert --alm --dry-run included) also prints
each migration-log event as its own JSON line before the final payload:
uft-migrate scan --source-root C:\UFT\Suite --run-id SUITE_SCAN --report-format both
jq -e '.go_no_go == "go"' out/SUITE_SCAN/scan_report.json
uft-migrate run --source-root C:\UFT\Suite --run-id SUITE_RUN --report-format both
jq -e '.approval.ready_for_approval == true' out/SUITE_RUN/executive_summary.json
An ALM run that aborts at the pre-flight gate writes no report set and
therefore prints no go_no_go at all (its analysis report set is preserved
untouched). Treat a missing verdict as no-go; jq -e '.go_no_go == "go"'
already fails closed on it. run --alm additionally publishes alm_status,
the ALM pipeline's own status word — preflight-failed for exactly this abort
(the gate refused and no ALM writes were performed) — because the
top-level status is the readiness rollup and only ever says failed.
Use --strictness strict if you also want the exit code to fail on scan
blockers. summary.unresolved_edges, summary.dependency_cycles and
nothing_assessed explain a filesystem no-go that has no blockers behind it,
metrics.assets_failed explains an ALM one, and summary.scan_review_required
explains a strict exit 2 that has no no-go behind it.
Environment check¶
uft-migrate doctor
uft-migrate doctor --alm-url http://alm:8099/qcbin --username svc.migration
Add --json for machine-readable output. The ALM password for the live
login check comes from the UFT_MIGRATE_ALM_PASSWORD environment variable
(--password is accepted too, but a command line is visible to other processes
on the machine). doctor also reads UFT_MIGRATE_ALM_URL and
UFT_MIGRATE_ALM_USERNAME in place of the two flags, and takes no --domain
or --project: the live check is a login, not a project operation.
doctor is the exception to everything in the previous section. It is a
read-only preflight dispatched before the run-folder machinery, so it writes no
run artifacts, prints no PROJECT_ROOT: banner, and prints a human-readable
check report unless you pass --json. Its status values are ok|warn|fail
(not ok|failed), and it exits 1 when any check fails — 0 otherwise,
warnings included. The checks themselves never exit 2; exit 2 from doctor
means argparse rejected the command line, for example an unsupported option
such as --config.
Filesystem workflows¶
# Analyze a suite
uft-migrate scan --source-root C:\UFT\Suite --report-format both --emit-dependency-graph
# Convert to a new output tree, reusing the prior scan's verdict
uft-migrate convert --source-root C:\UFT\Suite ^
--scan-report C:\work\out\SUITE_SCAN\scan_report.json --strictness strict
# Full closed loop in one command
uft-migrate run --source-root C:\UFT\Suite --report-format both
# In-place conversion (version-controlled sources only)
uft-migrate convert --source-root C:\UFT\Suite --in-place --overwrite-policy backup
# Validate a converted tree
uft-migrate validate --target-root C:\work\out\SUITE_RUN\converted
--scan-report does not choose what is converted. It only reuses the earlier
scan's verdict and inventory in the reports; the tests themselves are
rediscovered from --source-root (the current directory when you omit it), so
pass the same --source-root and the same --include/--exclude/
--exclude-test-path values the scan used.
--vbs-preservation-mode python-only|comment-block|interleave controls how
much original VBScript rides along in converted files (interleave is ideal
for review phases).
Taking tests out of scope, and where the drops are reported¶
--exclude-test-path <name|path|glob> (repeatable) marks a filesystem test
out of scope for this wave. The files are left untouched on disk; the test
is simply not converted. Matching is anchored, never a substring: a pattern
with no /, * or ? matches a whole path segment, so
--exclude-test-path Login excludes a test named Login and every test under
a folder named Login, but not Admin_Login.
An excluded test produces no row in convert_report.json — it was never part
of the wave — so the run headline still reads as a complete conversion. The
conversion narrative is written to stderr, one line per dropped or blocked
asset, and that is the only place an over-broad pattern shows up:
out of scope: C:\UFT\Suite\Login\Smoke (matched exclusion pattern 'Login')
excluded 4 out-of-scope test(s) from this wave; files left untouched
ignored as a Phoenix artifact, NOT converted: C:\UFT\Suite\Smoke.bak (Phoenix backup of 'Smoke' (folder 'Smoke.bak'))
[Checkout] BLOCKED: 2 blocker(s); no test built
Redirect it (2> convert_drops.log) on any run where scope matters. The
Migration Console already captures it to out\<run id>\run_cli_stderr.log
(analysis_cli_stderr.log for the analysis step). Before signing off, check
that the number of excluded tests is the number you intended.
--exclude-test-path is applied by conversion only. scan/analyze, and
the scan phase of run, still discover and measure an excluded test, so its
blockers stay in scan_report and in go_no_go. Unlike --alm-exclude-test-id
it does not change the analysis verdict; judge the wave's scope from the stderr
narrative above.
The ALM counterpart is --alm-exclude-test-id (see "ALM flags on convert").
ALM workflows¶
--alm-url, --username, --domain and --project are required on every
command that connects to a whole ALM project — convert --alm, run --alm
and restore. On convert and run a missing value stops the command with
"Missing required ALM configuration values"; restore declares all four as
required arguments, so it rejects the command line itself (exit 2). On those
commands only the password has an environment-variable source,
UFT_MIGRATE_ALM_PASSWORD, and that is the preferred way to supply it —
--password works but a command line is visible to other processes on the
machine. The Migration Console collects every connection value field-by-field
and passes the password through the environment variable, never on a command
line (gui_field_guide.md).
ALM flags on convert¶
ALM conversion is always whole-project — the entire domain/project is converted as one unit. There is no folder- or count-scoped ALM conversion.
It is also always in-place: the converted Python is written back into the
existing ALM tests, preserving their ids, links and history. There is no "new
tests" ALM mode — --convert-mode and --confirm-in-place are accepted on the
command line but ignored once --alm is set (the ALM options hard-code in-place
and the in-place confirmation), so the backup you assert with
--confirm-alm-backup is the real safety net. --convert-mode is live only on
the filesystem path, where --convert-mode in-place is the same selector as the
--in-place flag shown above.
Connection and target:
--alm · --alm-url · --username · --password ·
--domain · --project · --alm-test-plan-module-id
Scope:
--alm-exclude-test-id (repeatable) marks an ALM test out of scope for this
wave, on the conversion path as well as analysis. The test is left untouched
in ALM, and it does not count against the run's scope-coverage check. Park whole
call chains: if a parked test is called by a test that is still in scope, the
caller cannot rebuild its reference and the pre-flight gate refuses the wave —
park the caller too, or remediate instead. It is the ALM analog of
--exclude-test-path, and the Migration Console surfaces it as Step 5's
"Out-of-scope test IDs" parking field.
--alm-test-plan-module-id is optional and only affects the report's td://
test links: without it each test is named in plain text, with no link. It is
ALM's internal Test Plan module id, stored per project, so look it up for the
project you are converting; find it via the ALM client's Copy URL on any
test (the digits in TestPlanModule-<id>), or with
SELECT DISTINCT CSET_CATEGORY FROM COMMON_SETTINGS WHERE CSET_CATEGORY LIKE 'TestPlanModule-%'
in Site Administration → Database. See gui_field_guide.md.
Safety and writes:
--upload · --dry-run · --allow-blockers ·
--allow-partial-conversion · --confirm-alm-backup ·
--purge-uft-cache/--no-purge-uft-cache
--purge-uft-cache is the real cache control: it clears UFT's TD_80
test-extraction cache before every run, and again after any run that wrote to
ALM, on the machine Phoenix runs on. Every other execution host still needs
%LOCALAPPDATA%\Temp\TD_80 cleared by hand after a conversion.
--purge-alm-cache/--no-purge-alm-cache is accepted for compatibility and does
nothing.
From the Migration Console these are fixed rather than exposed: ALM
conversion always uploads, the Run Analysis workflow is always the
read-only dry run, the cache purge is always on, and --allow-blockers is never
set — a blocked asset makes the pre-flight gate refuse the whole conversion,
with no ALM writes, until you remediate it or park it in Step 5's "Out-of-scope
test IDs" field and re-analyze. The flags above remain available on the CLI as
support/expert levers.
Analysis:
--analysis-depth standard|deep — standard screens every test in parallel
(--threads worker processes, default 10) without building it in UFT (fast
go/no-go filter); deep AOM-builds each test serially (single UFT COM
constraint — conversion is serial for the same reason), proving each passing
asset actually converts. Deep analysis
reports additionally carry the Technical Conversion Breakdown — counts
measured by the same converter the real conversion runs, per asset and
overall: source/Python line counts, lines needing parentheses added for
Python call syntax, VBScript intrinsic replacements, and descriptive-
programming lines split static vs dynamic. This is the per-construct
evidence behind the go/no-go verdict. Filesystem analysis (scan) has no
depth concept and always includes the breakdown — its single mode fully
measures every script.
What ALM analysis does not look for. At either depth, an ALM analysis
reports what the converter and the Python syntax gate find. The
construct-review findings the filesystem scan raises — checkpoints and
OutputValue, the GetROProperty/GetTOProperty/SetTOProperty family,
.Object, Scripting.Dictionary, RegExp, Environment, OptionalStep,
recovery scenarios — are not raised on the ALM path, so an ALM project full of
them can still report go. Search your own source for those constructs if you
need them inventoried before a wave.
Execution verification (CLI-only support levers — not exposed in the console):
--run-via-alm (ALM Test Lab) · --alm-test-lab-folder ·
--alm-test-set-name · --alm-host · --uft-timeout
There is no local-execution lever. --run-uft was removed when the AOM
conversion path became canonical. It is still accepted so that it can be
refused by name instead of silently doing nothing — passing it fails the
command with exit 2 and a status: failed payload, on both the ALM and the
filesystem path. Nothing is converted, uploaded, or run.
How to actually verify a converted test runs. Conversion never proves execution, and neither does any flag on this page; the pipeline's own post-upload verification proves the bytes on the server are correct, not that the test passes. Do one of:
- Open the converted test in UFT One and run it. This is the check that applies to both paths and the only one available for filesystem conversions.
- Run it from ALM Test Lab — either from your existing test sets in the ALM
client (the normal path, and the only one available to a Migration Console
operator, since the console has no execution control at
all), or via
--run-via-almwhen a support engineer needs the run captured in the Phoenix report. Test Lab execution needs an active, unlocked interactive desktop on the execution host.
The safety model (read before your first upload)¶
- Nothing writes to ALM without
--upload. --dry-runfirst — but know which dry run you asked for.convert --alm --dry-runwithout--uploadis the analysis path: it downloads each test, converts the bodies, and reports go/no-go — blockers, external references, missing or unresolvable resources, checkout/lock state — with zero ALM writes and no backup confirmation required. Its depth matters:--analysis-depth standard(the default, and what the Migration Console's Run Analysis button uses unless Analysis Depth is set to Deep) is that AOM-free assessment, while--analysis-depth deepreally does launch UFT and AOM-build every test, proving each passing asset converts — still with no upload.
Adding --upload to a dry run makes it do less, not more: dry-run
conversion stops before any UFT/AOM operation, so --upload --dry-run
rehearses the gate, not the payload. It produces no AOM build and no
per-file upload or stale-file preview.
3. Real uploads require --confirm-alm-backup — your assertion that the
ALM project database and repository are backed up (or snapshot-protected).
4. Blockers fail closed. A test whose conversion produced blockers is
refused (status=blocked) before any ALM write; --allow-blockers is
the recorded expert override. See remediation.md.
The all-or-nothing pre-flight gate has its own recorded override,
--allow-partial-conversion: it converts the eligible assets when others
are disqualified, knowingly leaving the project half Python and half
VBScript. The gate still runs, every disqualified asset stays named in
alm_preflight.json, and those assets are re-injected as in-scope
preflight-disqualified failures — so a run that used the override can
never report a clean whole-project conversion.
5. Copy/paste-derived tests are refused (status=shared-assets) — their
assets belong to the source test and conversion cannot work. See
advanced_troubleshooting.md §1a.
6. Every upload is verified: stale server files are removed from a real
server listing, asset ownership is repaired so UFT runs the Python, and
the test is re-downloaded and byte-compared (upload_verified in the
results). A mismatch, or a verification download that itself failed,
fails the test as upload-verify-failed, and that aborts the run as item
9 describes: the test comes back as rolled-back (or rollback-failed),
with the verification failure kept in error and the run journal.
7. Version-controlled projects check in only after verification. In a
VC-enabled ALM project each converted asset is checked in only once
post-upload verification passes; any failure after check-out abandons the
checkout, so the server keeps its last checked-in version.
8. A conversion is judged against the analysis in its own run folder. Run
the analysis, any --upload --dry-run rehearsal and the real upload into the
same folder: pass the same --run-id to all of them, or omit --run-id
everywhere and pass the same --domain/--project (folder
<Domain>-<Project>). A conversion in a folder with no analysis is refused —
every asset is not-analyzed and the run ends preflight-failed with no ALM
writes. The Migration Console does this for you: it converts into the run
folder its analysis wrote. A rehearsal writes no report set and no executive
summary of its own, so the analysis reports in that folder — and the frozen
alm_analysis_results.json the gate judges against — are left as they are.
It republishes that folder's verdict rather than computing a new one, and
prints a report_set line explaining why it wrote no reports of its own —
unless the rehearsal itself failed an in-scope asset, in which case it
publishes no verdict at all; treat that absence, like any other missing
verdict, as no-go.
9. A real upload stops at the first failure it cannot recover. A failure
that happens before the asset's upload starts — a UFT build fault, an
error, or a test the Test Plan walk did not find — is retried up to twice,
and a dropped ALM session is reconnected first. Any other failure aborts the
run. Every asset this run already uploaded is then restored from its frozen
pre-conversion snapshot, callers first, and reported rolled-back (VBScript
again); the assets after it are reported not-attempted. rollback-failed
means the restore did not complete or there was no snapshot: that asset may
still be Python on the server, so inspect it in ALM and recover it with
restore (when a snapshot exists), ALM version history, or your backup.
Neither the rollback nor restore undoes the resource relation changes:
a restored VBScript test stays related to the converted .pfl and to
PhoenixVBRuntime.pfl rather than to its original .qfl, and the runtime
library and its Resources folder stay in the project. Re-point those
relations in the ALM client before running a restored test.
alm_safety.md has the full list of what a conversion writes.
What conversion carries automatically (ALM mode)¶
- Action parameters — definitions (name, type, direction, default) are harvested from the legacy test and authored onto the converted test.
- Linked function libraries (
.qfl→.pfl) — a library attached as an ALM Test Resource is downloaded and converted once to a Python Function Library (.pfl, the Python analog of.qfl), uploaded as a shared ALM "Function library" resource in the same Resources folder as the source.qfl(convert-in-place), and linked to each converted test (UFT Settings → Resources → Libraries). Phoenix does not rely on that link alone: it also injects a short auto-generated library-binding prelude at the top of every non-flow action of a library-linked test, which loads the library unconditionally on action entry (execinto the action's globals), so the library's functions are callable directly — noimport— and can use the UFT runtime objects (Reporter,SystemUtil, …). The main flow (Action0) gets no prelude, so a library function called directly from the converted main flow is not self-healed. The library is identified by its ALM resource (asset) id, not by file name: several same-named.pflfiles can live under different Resources folders, and UFT's Python engine was measured binding a library the test is not linked to. Because UFT scopes libraries to the calling test, each test also publishes its library choice before the main flow body in Action0, so an action invoked from another test binds the caller's library. Both of those generated blocks are marker-delimited and are stripped and regenerated on re-conversion — do not hand-edit them. Libraries are still shared by reference, not copied or inlined into actions: the library is converted once and every referencing test links the same.pfl, exactly like native UFT.qflbehavior. - A third generated block, and why it shows up in the canvas. Action0 of a
test that links a function library or carries a cross-test reference can also
end with a
# _phoenix_scope_bindingblock: anif False:suite ofRunAction("<name>", oneIteration)lines. It is never executed — it exists because UFT's Python engine only re-binds an action's linked libraries on iteration 2 and later when the main flow script names that action. UFT's Solution Explorer canvas and the engine's flow scan are the same static parse, so every line in the block is drawn as a flow node even though nothing runs it. The block lists only actions the main flow does not call itself — typically an action run from inside another action's body, or reached through an external reference — so a reviewer comparing the converted canvas against the VBScript original sees those nodes and no others. This applies to filesystem conversions as well as ALM ones. Like the other two blocks it is marker-delimited and regenerated on re-conversion; do not hand-edit it. - VBScript compatibility helpers (upload conversions only) — kept in the
shared function library
PhoenixVBRuntime.pfl, in a Resources folder namedUFT Phoenixthat is created on first use and only ever gains entries, so a script keeps running the exact helper text it was converted with. The library is linked and related to each converted test whose scripts use it, and those scripts carry a generated# _phoenix_vb_runtimeblock — placed after any library-binding prelude — that installs the helpers they need;alm_aom_results.jsonlists them per test asruntime_helper_blocks. If the library cannot be written or related to a test, that test simply keeps its helpers pasted inline and runs as it always did. A test fails (status error) only when a library it links, or an action it calls, already depends on the runtime, because then there is no pasted copy to fall back to. Analysis never writes or uses the runtime. Do not hand-edit the block or the library. The filesystem path keeps its own copy atFunctionLibraries\PhoenixVBRuntime.pflunder the output root. - Object repositories and DataTables — carried per action, byte-identical.
One open limitation: a caller that owns a shared
.tsrrepository can lose that association. The build discloses it when it happens, and converting the test again does not repair it — re-attach the shared repository in UFT. - Cross-test shareable ("reusable") action references — when a converted
caller invokes an action that lives in another test (
RunAction "X [OtherTest]",LoadAndRunAction), those external references are rebuilt natively (via UFT'sAddExistingAction) so the shareable action stays in its own test and is consumed by reference. The action body is never copied into the caller. Because the whole project is converted together, every test in a call chain is Python and the chain runs under one engine. - UFT API/Service tests (C#-based) are detected and skipped as out of scope.
Version-controlled ALM projects¶
Projects with ALM version control enabled are supported automatically — no configuration. Versioning is detected per asset through OTA (a project without version control is simply treated as unversioned).
Conversion flow per asset:
- Clear any entity lock — best-effort, and never a failure (see below).
- Undo any pre-existing checkout — a checkout left by another user, or a stale one of our own, is undone first. This reverts the server working copy to the latest checked-in version, and that reverted state is exactly what gets converted.
- Fresh check-out → convert → upload → post-upload verification.
- Check-in only after verification passes, with the comment
UFT Phoenix migration: AOM-canonical Python conversion (verified).
Any failure after the checkout abandons the checkout (UndoCheckout), so
the server keeps its last checked-in version — a failed conversion never
leaves a half-written working copy or a bad checked-in version. Python
function-library resources (.pfl, ALM "Function library" resources) are
checked out and checked in the same way, but not on the test's timetable:
each is checked in as soon as its content is uploaded — before the test's
own build and verification gates run — so a later failure of that test, or
an abort rollback, does not revert the library. Its previous version stays
in ALM version history, and the run notes name the saved copy of the prior
bytes.
Undoing another user's checkout requires the connected ALM user to hold
the ALM "Manage checkouts"-equivalent permission; without it the conversion
stops at that asset with report category "Version control blocked" (status
vc-blocked). ALM conversion is all-or-nothing, so the run aborts there: assets
this run already uploaded are rolled back to VBScript (rolled-back) and the
rest are recorded not-attempted. Grant the permission (or have the holder
release the checkout), then re-run the whole-project conversion into the same
run folder as the analysis — the rolled-back assets convert again, and anything
the journal recorded as finished is carried forward. There is no failed-asset-
only re-run of an ALM conversion, and --resume does not apply.
Every conversion in a version-controlled project creates a new version;
the pre-conversion version stays in ALM version history, which is a
first-class rollback path (alongside the restore command).
Asset locks never fail a conversion. A locked asset is converted and
overwritten; there is no locked failure status. Per asset the pipeline
allows a 10-second grace period for the lock to clear on its own, attempts
to release it, then converts either way. That is safe because an ALM lock
does not block ExtendedStorage.Save — the write that carries a test's
payload (Script.pts, Test.tsp, the .usr) — only entity metadata
writes (SetField/Post), which the upload path already treats as
optional. It is also necessary: a whole-project conversion is
all-or-nothing, and a run that dies partway leaves the project half Python
and half VBScript, breaking cross-test action chains. UnLockObject()
releases only your own lock; revoking a foreign one means deleting its
LOCKS row through the OTA Command object, which ALM disables by default
— where that is unavailable the conversion still completes and overwrites.
The run records entity-lock-force-revoked or
entity-lock-not-cleared-proceeding, and the asset carries a note naming the
holder. When the LOCKS table is readable that note also names the machine, ALM
session id, lock time and last-active time; when it is not, only the user name
is available. Because this overwrites anyone still editing a locked asset, run
conversions in an agreed window with the project empty.
Analysis makes no version-control writes. The ALM analysis
(convert --alm --dry-run) only snapshots per-asset checkout/lock state.
On a version-controlled project the analysis report includes a "Version
Control & Locks" section — counts plus who holds each checkout and lock —
the operator's chase list before a conversion window.
Examples¶
Every command below needs the four connection flags, and the analysis and the
conversion must land in the same run folder — here WAVE1. Set the password
once for the session (Windows cmd):
set UFT_MIGRATE_ALM_PASSWORD=<password>
# 1. Analyze the project (read-only: zero ALM writes).
# This is what the Migration Console's Run Analysis button issues for an ALM
# project. There is no `analyze --alm`: that flag combination exits 2.
uft-migrate convert --alm --dry-run --run-id WAVE1 ^
--alm-url http://alm.example.com:8080/qcbin --username svc.migration ^
--domain MY_DOMAIN --project MY_PROJECT
# 2. Rehearse the gate for a whole-project upload (zero ALM writes; no AOM
# build, no per-file upload preview — see the safety model above).
# It writes no report set of its own, so step 1's analysis reports stay put;
# stdout carries that folder's verdict and a `report_set` line saying so.
uft-migrate convert --alm --upload --dry-run --run-id WAVE1 ^
--alm-url http://alm.example.com:8080/qcbin --username svc.migration ^
--domain MY_DOMAIN --project MY_PROJECT
# 3. Real whole-project upload, in place, with the executive summary.
# `run --alm` is the command the Migration Console's Run Conversion issues;
# `convert --alm` runs the same pipeline but writes no executive_summary.*.
uft-migrate run --alm --upload --confirm-alm-backup --run-id WAVE1 ^
--alm-url http://alm.example.com:8080/qcbin --username svc.migration ^
--domain MY_DOMAIN --project MY_PROJECT
# Same upload, plus an ALM Test Lab run of the converted tests
# (support lever; the Migration Console has no execution control).
uft-migrate run --alm --upload --confirm-alm-backup --run-id WAVE1 ^
--alm-url http://alm.example.com:8080/qcbin --username svc.migration ^
--domain MY_DOMAIN --project MY_PROJECT ^
--run-via-alm --alm-test-set-name "Wave 1"
Drop --run-id WAVE1 from all of them and the folder defaults to
<Domain>-<Project> instead (out\My_Domain-MY_PROJECT for the values above),
which is equally valid as long as every command passes the same
--domain/--project. What must not happen is a conversion in a folder that
holds no analysis: that run is refused at the pre-flight gate.
Cross-test reusable actions (whole-chain rule)¶
A test that calls another test's reusable action executes the callee inside
the caller's engine, so a chain must be uniformly one engine. Phoenix
converts the whole ALM project together, then rebuilds each caller's external
"call to existing action" references natively with AddExistingAction — the
shareable action stays in its own test and is consumed by reference (no
copying, no inlining of the action body). Resolved references are recorded per
asset in alm_aom_results.json and conversion_summary.json
(external_test_paths, action_renumber_map), and the cross-test graph is in
analysis_dependency_graph.json.
ALM run artifacts¶
In the run folder, after an ALM analysis or conversion:
scan_report.{json,md,html}and theanalysis_*.htmldrill-down pages — the verdict and the per-asset evidence behind it.alm_aom_results.json— per-test statuses, blockers, upload verification and Test Lab run ids. The authoritative per-asset payload.alm_analysis_results.json— the analysis payload frozen when the pre-flight gate passed, so the conversion cannot overwrite the evidence that authorised it.alm_preflight.json— the pre-flight gate's verdict and every disqualified asset by name.conversion_summary.jsonandobservability_metrics.json— run-level rollups.analysis_dependency_graph.json— the cross-test shareable-action graph, written by the analysis pass.dependency_graph.json— the graph the report's Dependency Graph JSON link opens; on ALM runs it is built fromanalysis_dependency_graph.json.migration_log.txt— JSON-lines event log.file_manifest.txt— flat manifest of the analysed scope.resume\alm_test_results.ndjson— the per-asset conversion journal that drives Carried Forward.resume_state.json— the run checkpoint the Resume Previous Run dialog reads.alm_aom_work\alm_rollback\— the frozen pre-conversion snapshots. These are the true originals and whatrestorereads;alm_aom_work\test_<id>_sourceholds only the latest download.executive_summary.{json,md,html}— written byrun --alm(and bysummarize), not byconvert --alm.
Forensic reading order: advanced_troubleshooting.md §5.
Config file fields¶
search_folders: ordered extra resource lookup roots for relative reference resolution. Config file only.custom_rule_mappings: regex → replacement rewrites applied to every converted line (editable from the Migration Console's mapping-rule editor). Config file only.reporting.redaction: redact path fields in generated artifacts. Config file only.uft_object_model_path(also--uft-object-model): a UFT object model JSON file. It replaces the shipped method registry rather than extending it, so use it only to supply a complete model; to add a handful of framework methods usecustom_uft_methodsinstead.custom_uft_methods(also--custom-uft-method): methods always emitted with parentheses.reporting.verbosity:quiet|normal|verbose(--verboseselectsverbose).reporting.emit_dependency_graph(also--emit-dependency-graph): toggle the dependency graph artifact.
There is no allow-list for dynamic descriptive programming, and nothing blocks
it: it is classified "dynamic (requires review)", carries a risk weight, and is
surfaced as the report row "Descriptive programming - dynamic (requires
review)". Do not confuse it with the separate dynamic-code (Execute/Eval)
blocker, which no config field waives either.
Custom method onboarding flow¶
- Run
scanand reviewunrecognized-object-memberwarnings. - Add framework methods to
custom_uft_methods(config file,--custom-uft-method, or the console's Custom UFT Methods box). - Re-run
scan; convert once the warnings are triaged.
Legacy ALM source roots (not recommended)¶
A filesystem scan/convert whose --source-root is an ALM reference
([ALM]..., td://, qc://, alm://) needs an external bridge program, named
in the UFT_ALM_BRIDGE_CMD environment variable, with an optional
UFT_ALM_CACHE_ROOT for its local cache. Phoenix does not ship a bridge;
without one the scan reports the unresolved-alm-source blocker and nothing is
converted. Use the whole-ALM-project path described above instead.