Known Limitations¶
The complete conversion coverage matrix — what converts automatically, what is approximated with warnings, and what fails closed — lives in supported_versions.md. This page covers the boundaries and caveats that apply to the current release.
Phoenix offers exactly two conversion paths: local filesystem conversion
of UFT test directories on disk, and whole ALM project conversion. Both are
AOM-canonical — each test is opened, converted, and re-saved through UFT's
own Automation Object Model, producing a structurally valid, runnable UFT 26.1
Python test (never hand-patched binary Test.tsp/.usr, never a source-only
transpile). Both therefore require UFT installed locally; the filesystem path
saves to a local directory, the ALM path saves back into ALM (preserving each
test's ID). ALM conversion is all-or-nothing for a given domain/project — you
cannot convert an arbitrary subset. This is a hard rule, not a limitation to
work around: a callee action runs inside the caller's script engine, so a
call chain must be uniformly one engine (all Python). Converting the whole
project keeps every chain uniform and lets Phoenix rebuild cross-test
shareable-action references natively (see below). The CLI-only
--allow-partial-conversion override exists, but it knowingly leaves the
project half Python and half VBScript and is not a safe way to convert a
subset; see alm_safety.md.
Conversion boundaries (fail-closed)¶
These constructs are refused rather than converted. ALM project conversion
always runs at strict, so each one blocks its test and stops the
whole-project run unless it is overridden with the CLI-only
--allow-blockers (the GUI has no override). Local filesystem conversion
follows its Strictness setting, which changes what "refused" means — see
Strictness on the local path below.
- Dynamic code —
Execute/Evalused as a statement (ExecuteGlobalis refused the same way). No deterministic translation exists; replace it in the source with explicit dispatch (a custom mapping rule cannot clear this blocker). (Evalinside an expression is a different case: the filesystem scan raises it, but neither path refuses it — see below.) ExecuteFile— everyExecuteFilestatement is refused, whether or not the library it loads is linked to the test. Link the.qflto the test (as an ALM Test Resource for ALM conversion, or on the test's library list for local conversion) so it is converted once to a Python Function Library (.pfl) and linked automatically, then delete theExecuteFileline — linking the library does not clear the blocker.Parameter()names with nothing to resolve against. Action-level and test-level parameter definitions are both harvested from the source test and re-authored on the converted one, and a main-flowParameter()read resolves against the test-level set. What fails closed is a name that is in neither: in an action with no action-level definitions of its own — the main flow always — aParameter()name absent from the test-level definitions blocks the test. Only default values are carried; test-set run-time overrides do not propagate. The check is per action and needs UFT, so it runs during a conversion or a Deep ALM analysis, never in a standard ALM analysis. On a local filesystem conversion this blocker is waived at the defaultstandardstrictness, and unlike the constructs above it leaves no trace in the script: theParameter()call is published unchanged, with no# SKIPPEDcomment, and the test fails at run time with UFT'sParameter <name> not found. Usestrictif you want the conversion to refuse it.- Cross-test reusable-action callers require whole-project conversion.
A test that calls another test's reusable actions (
RunAction "X [OtherTest]") executes the callee inside the caller's engine, so the whole chain must be uniformly Python — a mixed VBScript/Python chain fails at runtime (a VBScript caller executing a Python callee errors on line 1). This is why ALM conversion is whole-project: converting the entire project together keeps every chain uniform and lets Phoenix see inbound references from any test. When a caller is converted, its external "call to existing action" references are reconstructed natively (via UFT'sAddExistingAction) so the shareable action stays in its own test and is consumed by reference — the action body is never copied or inlined into the caller.
Strictness on the local path. strict refuses the test: the construct
becomes a # BLOCKED comment and nothing is built. standard — the shipped
default — still builds and publishes the test, with the construct replaced by
a # SKIPPED unsupported … comment and the blocker recorded as waived
(OVERRIDE: proceeding despite N blocker(s) in the conversion notes).
permissive publishes a raise NotImplementedError(...) stub in its place and
records only a warning. At standard the construct is silently skipped rather
than failed; at permissive it stops the action the moment it is reached. In
both cases the verdict alone will not tell you, so read the conversion notes as
well.
Checkpoint/OutputValue objects and GetROProperty-family APIs are flagged
by the local filesystem scan only. There they are scan-report errors:
they turn the run's verdict to no-go and are listed per line. ALM project
analysis does not run those detectors at either depth, so an ALM project full
of checkpoints can still report go. On both paths the line itself is still
converted (the UFT Python API's property model differs), so review every such
line before you approve the run. Before an ALM conversion, find them in your
own source:
Get-ChildItem -Recurse -Include Script.mts,*.qfl,*.vbs |
Select-String -Pattern '\.(Check|Output)\s*\(?\s*CheckPoint\s*\(|GetROProperty|GetTOProperty|SetTOProperty'
For an ALM project, run it over an export or over the downloaded folders named in the next section.
Not detected before conversion — find these yourself (known gap)¶
The local filesystem scan does not classify the first three constructs
below. They raise no blocker and no finding that names them — at most a generic
"Call statement or implicit-call syntax detected" warning, because the line
parses as an ordinary procedure call — and warnings do not affect the go/no-go
verdict. A filesystem scan of an estate whose only issue is a GoTo still
reports go_no_go: "go"; the conversion then refuses to build that test with
"converted output is not valid Python".
ALM project analysis is the exception. It parses every converted action body, so those three are refused at analysis time by that same "converted output is not valid Python" blocker, which names the converted action and — where it can map it — the source line number, and cannot be overridden. That message describes itself as a converter defect — for the constructs below it is not one, and the fix is in the VBScript.
Search your estate for all four and remediate the VBScript before you convert:
GoTo <label>(and its inline formIf … Then GoTo <label>) — the label and the jump are emitted verbatim; the action does not compile.On Error GoTo <label>— emitted verbatim; the action does not compile. (On Error Resume NextandOn Error GoTo 0are supported.)- VBScript classes —
Class…End Class,Property Get/Let/Set— emitted as calls (Class(Cart),Public(Property Get T)); the action does not compile. Evalinside an expression (x = Eval("…"), as opposed to the statement formEval "…") — emitted as a call to an undefinedEval, which does compile and then raisesNameErrorat run time. The local filesystem scan does raise this one, as a dynamic-code error that turns the verdict tono-go; ALM analysis is silent on it, precisely because the Python it produces parses. It is the one case in the list that reaches a live run.
A pre-migration sweep of the source, run from the root of your test estate, finds them:
Get-ChildItem -Recurse -Include Script.mts,*.qfl,*.vbs |
Select-String -Pattern '(?<![\w.])(GoTo\s+(?!0\b)\w+\s*(''.*)?$|Class\s+\w+\s*(''.*)?$|Property\s+(Get|Let|Set)\s+\w+|Eval\s*\()'
It deliberately keeps On Error GoTo 0 and member access such as
obj.Class out of the results; treat every remaining hit as a review item
(a match inside a comment or a string literal is a false positive), and note
that a GoTo or Class statement followed on the same line by a :
separator will not match.
For an ALM project, run the same sweep over an export, or over the folders a
GUI Analysis or convert --alm --dry-run downloads under
out\<run id>\alm_aom_work\ in the folder you launched Phoenix from:
test_<id>_source holds each test's scripts and test_<id>_libraries its
linked function libraries. Remediation is ordinary VBScript refactoring:
replace GoTo with structured flow, replace a class with a function library,
and replace Eval with explicit dispatch.
The filesystem scan does not classify GoTo, On Error GoTo <label> or
VBScript classes, so that path's verdict does not reflect them the way the ALM
path's does; this manual sweep is the control.
Semantic approximations (converted, with per-file warnings)¶
Empty,Null, andNothingall convert to PythonNone; code that distinguishes them (IsEmptyvsIsNullvsTypeName) is approximate.Eqv/Impconvert with boolean semantics: exact when the operands are conditions (If a = 1 Eqv b = 2), approximate on integers, where VBScript is bitwise (5 Eqv 3is -7) — those sites are flagged per file and need review.Xoris not approximate: it converts exactly, integers included (5 Xor 3is 6 in both languages).- Out-parameters do not propagate. VBScript passes ByRef by default, so a
procedure that assigns one of its own parameters updates the caller's
variable; Python parameters are always by value, so the caller keeps its old
value. This applies whether or not
ByRefwas spelled — the keyword is almost never written in practice. Every procedure that WRITES a parameter is flagged per site — by the local filesystem scan, and by the converter's own per-file warnings on both paths — naming the parameters involved. Rewrite those helpers to return their value(s). Two cases are correctly not flagged because nothing is lost: an explicitByValparameter (VBScript does not propagate it either) and an element write into an array parameter (arr(0) = x, which propagates in both languages). That concerns the out-parameter warning only. The local filesystem analysis separately raises an error-severity finding on any literalByReforByValkeyword, which turns the verdict tono-go; see supported_versions.md. MsgBox/InputBoxconvert to non-blocking shims (print / default-return) so batch runs never hang on a dialog; tests designed around human acknowledgement change behavior by design.
Test-level artifacts not carried¶
A converted test is rebuilt through the AOM, not copied. Only what Phoenix explicitly re-applies survives; everything else in the source test comes out at UFT's defaults. That is the rule to plan against, and it is the safer way round from a list of known gaps, because the gaps below are the cases we have measured and can advise on — they are examples, not an exhaustive inventory.
What is re-applied in both conversion paths:
- the action map and action names, and cross-test shareable-action references
(rebuilt natively via
AddExistingAction) - per-action object repositories (copied, and content-hash verified)
- the root
Default.xls/.xlsxDataTable workbook - action-level and test-level parameter definitions
- the linked function libraries
What only ALM project conversion re-applies:
- the per-action shared-repository (
.tsr) associations — subject to the open defect documented below - six
Settings.Runfields: iteration mode, start and end iteration, on-error behaviour, object sync timeout, disable smart identification - the associated add-in set
A local filesystem conversion carries none of those three, and the consequences are concrete: a single-iteration source test comes out running every DataTable row; a shared object repository is not associated at all, with no warning; and the built test is saved with whatever add-ins the UFT session happens to have loaded, which changes which class claims a window (see the add-in note below). Set these on the converted test in UFT, or convert through ALM.
The counts in brackets below are how often each gap was hit in the reference validation (116 tests, 239 actions). They are evidence that the gap is real, not a statement of how often it will affect you.
- UFT Environment variables are not carried. Neither internal user-defined
variables nor the association to an external environment XML file. Nothing in
the rebuild reads or writes them, so a converted test starts with an empty
Environment and every
Environment("name")read returns nothing. Local filesystem analysis flagsEnvironment(...),Environment.ValueandEnvironment.LoadFromFileusage; ALM project analysis does not. Search for them before you convert — for scripts on disk, for exampleGet-ChildItem -Recurse -Include Script.mts,*.qfl,*.vbs | Select-String -Pattern 'Environment\s*(\(|\.Value|\.LoadFromFile)'; for ALM tests, search the test and library scripts in UFT or ALM — then re-create the variables on the converted test or move them into the DataTable. - An externally linked Data Table is not carried. Only a
Default.xls/.xlsxsitting at the test root is copied. A test pointed at a Data Table elsewhere (Settings → Resources → Data Table) comes out on the rebuilt test's own workbook, silently losing its data source. - Run-logic and configuration profiles (
default.usp,default.cfg) are not carried. They are generated by the AOM for the rebuilt flow. The run order survives because Action0's script is re-emitted with itsRunActioncalls, and on the ALM path the sixSettings.Runfields above are re-applied — but anything else those profiles held (additional Vuser profiles, per-action run-logic customisation) is not. Every test in the reference estate shared one identicaldefault.usp, so that validation could not have detected this and did not. - Action descriptions are not carried. Each rebuilt action is authored with an empty description.
- Recovery scenarios are not carried. [6 of 116] UFT 26.1 does not
support recovery scenarios for Python tests at all, so there is nothing to
carry them into. UFT 26.3 adds support but requires them re-authored as
Python (
.prs), a different file format from the VBScript.qrsthe source references — so this is not a gap Phoenix can close on 26.1 at any effort. Any test relying on a recovery scenario to survive an unexpected dialog, crash or session state will behave differently after conversion: the recovery never fires. Identify these before you convert — neither analysis path is a reliable detector. ALM project analysis records a test's linked recovery scenarios only as a per-asset note (Linked ALM Test Resources: … Recovery scenario) inalm_analysis_results.json; it raises no finding, does not show in the HTML reports and does not affect the verdict. The local filesystem scan counts the.qrsfiles it finds under the source roots, but warns only when a single script line both names a.qrsfile and contains the word recovery, and a scenario associated in Test Settings is never named in script text. Check File → Settings → Recovery in UFT, or the test's Test Resources in ALM, and either re-author the scenarios in 26.3 or handle the condition in the script. - Analog recording tracks (
AnalogTrackList.dat) are not carried. [2 of 116, both inert] Analog replay records literal mouse paths and keystroke timing; it has no Python equivalent. A test that genuinely callsRunAnalogstill converts, with no blocker and no finding that names it, but the analog track it replays is not carried, so those steps cannot replay. Re-author them as ordinary object steps. In the reference estate both affected tests carried the file without ever invoking analog replay, so nothing was lost there — do not read that as the general case. Search your estate forRunAnalogbefore converting. - Debug watch expressions (
Watches.xml) are not carried. [12 of 116] IDE-only debugging aids saved with the test; they have no effect on execution. Re-add them in UFT if you want them. No action required. - Record-and-Run / launcher settings are not round-tripped. [all tests]
The test's Record and Run Settings — which browser or application UFT
launches — are left at the converted test's defaults rather than copied.
This is deliberate: the AOM exposes a writable
Browseron the Web launcher but no consistentActiveflag across launcher types, so copying the source's values can make UFT launch the wrong application. A test that depends on Record and Run Settings to start its AUT will not start it after conversion. Set Record and Run Settings on the converted test, or drive launch from the script withSystemUtil.Run— which is what the reference estate does throughout.
Known defect (ALM path): a shared object repository can be dropped on a caller test¶
Status: open, disclosed at conversion time, not yet root-caused. This concerns ALM project conversion, the only path that re-applies shared repositories at all; a local filesystem conversion drops every one of them, as the list above says.
A test that both (a) calls another test's reusable actions and (b) associates a
shared object repository (.tsr) may lose that association during
conversion. Measured across the reference estate: 17 tests carried a shared
repository, 16 kept it, and the one that lost it was the only test that was
both a cross-test caller and a repository owner. Tests that are one or the
other are unaffected.
The consequence is severe where it happens: every object that resolves through
the repository becomes unresolvable, and the test fails with
Cannot find the "<object>" object's parent. It is also the combination most
likely to occur in a mature estate, where shared repositories and
reusable-action chains are both common.
ALM project conversion reports this per asset rather than dropping it
silently — look for [warning] shared object repository NOT carried into the
rebuilt test: <repository> in the conversion notes.
Do not check for this in ALM — it will lie to you. The ALM asset relation
between the test and the .tsr resource survives even when the action-level
association is lost, so the repository still shows as a linked resource on the
test while no action actually uses it. UFT resolves objects through the
per-action association stored in Action<N>/Resource.mtr, not through the ALM
relation. Verified on the affected test: the ALM relation to the repository was
intact, and the live action carried no association at all. The only reliable
checks are the conversion warning above, or running the test.
Until it is fixed: after an ALM project conversion, check the notes for that warning and re-associate the repository on the affected test in UFT (Resources → Associate Repositories). Note that re-running the conversion does not repair an already-affected test — the converter faithfully carries what the source has, the harvest reads the live action, and the association is already gone from it. Restore the affected test from a pre-conversion snapshot first, or re-associate by hand.
Two further differences are expected and benign rather than limitations, but
are called out because a byte-level comparison of source against build will
surface them: each converted action receives a new GUID, author and
timestamp (it is a newly authored asset), and action folder numbers may
shift (a source Action1 can land as Action2, because UFT's action-folder
allocator never reuses a number). Action names — which are what scripts and
cross-test references resolve by — are preserved exactly, as are the object
repositories and the DataTable. The run configuration and the shared-repository
associations are preserved on the ALM path only; on the filesystem path expect
them to differ, per the re-apply list above.
A third difference applies to the ALM path, looks alarming, and is not: the
converted test's .usr file lists fewer add-ins than the source's. That
section records which add-ins were installed when the test was saved, not
which the test is associated with, and it is typically identical across an
entire estate. Phoenix carries the associated set reported by
GetAssociatedAddinsForTest, which is the minimal set the test actually
needs — a test driving Browser/Page objects gets Web; one driving
WpfWindow gets WPF and UI Automation. This is
deliberate and load-bearing: an over-broad add-in set changes which class
claims a window, and has been measured to break object identification outright
(WpfWindow stops matching and every object beneath it fails). Compare
add-ins with GetAssociatedAddinsForTest, not with the .usr. A local
filesystem conversion carries no add-in set at all, so the built test keeps
whichever add-ins the UFT session had loaded — check and set them by hand.
What the reference validation did and did not exercise¶
A clean conversion only proves the paths the source estate actually walked. The reference estate (116 VBScript tests, 239 actions) did not exercise the features below, or exercised only the narrow slice each row names, so while Phoenix has unit coverage and, in several cases, explicit handling for them, they have not been proven end-to-end on a real conversion. Treat any of these in your own estate as needing a pilot conversion and a run comparison before a production wave — convert a representative handful first rather than the whole project blind:
| Not exercised | Why it matters |
|---|---|
Environment variables (Environment(...)) and external environment XML files |
Not merely untested — not carried at all; see the entry above. No test in the reference estate used them, so the gap was never exercised end-to-end either. |
| Active Screen data | Not carried and never exercised; Active-Screen-dependent maintenance workflows do not survive. |
| Virtual objects | No conversion path has been demonstrated. |
LoadFunctionLibrary and ExecuteFile at run time |
The linked-library path is well covered (39 tests); loading a library dynamically from script is not. Every ExecuteFile statement is refused on the ALM path; on the filesystem path its treatment follows the Strictness setting (see Strictness on the local path). |
Runtime Recovery.* API calls |
Distinct from recovery scenarios above; the runtime object is untested. |
WMI beyond Win32_Process (winmgmts:) |
Only Win32_Process ran end to end — enumeration (InstancesOf), a filtered ExecQuery, property reads, collection .count and Terminate(), in the reference estate's process-closing helper (see the process-scan note below). Other namespaces and classes have unit coverage only. |
Checkpoints are the exception to read carefully. They were exercised: the
reference estate's converted tests ran them, and its approved regression set
reported the same checkpoint count as its VBScript baseline. What is missing is
detection — ALM project analysis does not flag a checkpoint, an OutputValue or
a GetROProperty-family call at all, so find them yourself with the sweep above
and review every such line before you approve the run.
The constructs Phoenix is known not to detect before a filesystem
conversion — GoTo, On Error GoTo <label>, VBScript Class and
Property Get/Let/Set — also did not appear in the reference estate, and nor
did Eval inside an expression, which the filesystem scan does raise. The
pre-migration sweep above is the control for all four; run it.
Function libraries (.qfl → .pfl, shared and linked)¶
A VBScript function library (.qfl) linked to a test is converted once to
a Python Function Library (.pfl, the Python analog of .qfl) and linked
to each converted test (UFT Settings → Resources → Libraries) — the same in
both conversion paths, differing only in where the shared .pfl lives:
- ALM project conversion — the
.pflis created as an ALM Test Resource of type "Function library" in the same Resources folder as the source.qfl(convert-in-place) and linked by its[QC-RESOURCE]path. - Local filesystem conversion — the
.pflis written once to a sharedFunctionLibraries/directory beside the converted tests and linked by its on-disk path. That path is stored absolute, and cross-test shareable action references are likewise rebuilt against the callee's absolute published action folder — so the converted tree is not relocatable: run it from the output root it was written to, or re-run the conversion with the final location as--output-root. Every such binding is listed in the "Portability Manifest" section ofconvert_report.html. - Libraries linked from outside the source roots are converted but not
analysed. Design-time library linkage lives in the binary
Test.tspand is readable only through the AOM, so the analysis (which reads the source roots from disk) cannot see it: a library a test links from a folder outside every source root gets no inventory entry, no planned-transformation row and no blocker assessment, while the conversion still converts and links it. Add the folder holding the linked libraries to the source roots and re-run the analysis to have them assessed. A linked library missing from disk blocks its own test at every strictness. - Keep shared libraries out of test folders under Convert Mode = in-place.
In-place publishing replaces each test directory; what happens to the original
is the Overwrite Policy's decision. Under
backup— which is also what the defaultfailbecomes in in-place mode, since it would otherwise refuse every test — the directory is renamed to<name>.bak; underoverwriteit is replaced outright. Either way every non-test file inside it goes with it, including a.qflstored under the test folder. Phoenix links the already-converted.pflit produced from that library so the other tests still convert, but the library is not re-translated on later runs (its source is gone), so a converter fix cannot reach it. Store shared libraries in a folder no test publishes over, or convert with Convert Mode = new.
At run time UFT loads the linked .pfl into every action's namespace, so its
functions resolve directly, with no import, and can use the UFT runtime
objects (Reporter, SystemUtil, …). Libraries are linked, not inlined:
the library is converted once and every referencing test links the same shared
.pfl, exactly like native UFT .qfl behavior. Library procedures are never
copied into each action's Script.pts.
VBScript compatibility helpers live in PhoenixVBRuntime.pfl¶
Converted scripts need Python versions of VBScript built-ins (Right, InStr,
CDate, Err, MsgBox, …). Phoenix writes those helpers once, to a shared
function library named PhoenixVBRuntime.pfl, instead of pasting a copy into
every action and every converted library. Each script that uses them carries a
short generated block marked # _phoenix_vb_runtime that lists the helpers it
needs. The block sits at the top of the script, or directly after the
# _phoenix_library_binding block when the test links a function library.
- Where it lives. ALM project conversion: an ALM "Function library" resource
in the Resources child folder
UFT Phoenix, created by the first conversion that needs it, linked to every converted test whose scripts use it — or that calls an action whose scripts use it — and related to that test so Test Lab delivers it. Local filesystem conversion:FunctionLibraries/PhoenixVBRuntime.pflbeside the converted libraries, linked by absolute path and listed in the Portability Manifest. - What runs is unchanged. The block installs the same helper text into the script's own namespace, in the same order, at the point where the pasted copy ran — after any linked function library — so a function in a customer library that shares a helper's name behaves exactly as before. A helper the converter altered inside a particular script stays pasted in that script.
- The file only grows. Each helper entry is named with a digest of its text, and conversion only ever adds entries. A test converted by an earlier Phoenix version keeps running the exact helper text it was converted with.
- On the filesystem path, Overwrite Policy can stop it growing. With
Convert Mode
newand Overwrite Policyfail(the default), the file is never rewritten. So if a later conversion into the same output root needs helper entries the existingFunctionLibraries\PhoenixVBRuntime.pfllacks — typically after upgrading Phoenix — or finds one of its entries edited, each such test is blocked with "shared function library … already exists with different content and overwrite policy is 'fail'". Usebackuporoverwritefor that run (existing entries are kept, so tests converted earlier are unaffected), or convert into a new output root. - Do not edit it. A converted script runs whatever text the file holds
under the names it lists, so an edit takes effect on the next run. The next
conversion that writes the file checks every entry against its digest: an
edited entry for a helper the current version still has is put back to its
original text, and an edited entry for an older helper is dropped, after which
scripts that name it stop with the error described in
troubleshooting.md. ALM conversion reports such a
rewrite as replaced content and keeps the prior copy; a filesystem conversion
needs Overwrite Policy
backuporoverwriteto rewrite the file at all. - Analysis writes nothing. Standard and deep analysis leave the helpers pasted in what they build, and create no library.
- Report line counts still include the helpers. "Python lines produced" and "Changed Lines" measure the converter's output, which is produced before the helpers move into the library.
- Mixed estates are fine. A script converted by a Phoenix build that
predates
PhoenixVBRuntime.pfl— recognisable by pasted helper definitions and no# _phoenix_vb_runtimeblock — keeps its pasted helpers until it is converted again; both forms run side by side. - A converted library already on the server keeps its pasted helpers. If
the existing copy of a converted function library still carries pasted
helpers, tests outside the current conversion may load it without being
related to
PhoenixVBRuntime.pfl, so conversion rewrites it with its helpers pasted. Only a library that is new, or already uses the runtime library, is written without them. To remove the pasted helpers from such a library, delete its converted.pflresource and convert the whole project again. - If the runtime library is unavailable, nothing breaks. ALM conversion
relates
PhoenixVBRuntime.pflto a test before any of its scripts depend on it. If the library cannot be written, read or related, that test keeps its helpers pasted and converts exactly as before, and the conversion notes say "helpers stay pasted". A test fails only when something it links is already compacted on the server and cannot fall back.
Measured on the reference estate: a whole-project conversion moved the helpers of 45 tests into the runtime library and related it to 6 more that call actions using it; all 51 were then run in Test Lab. None failed on the library or a missing helper. The approved regression set of 81 tests, 31 of which use the runtime library, passed with the same status and the same checkpoint count as its VBScript baseline.
Library links are fixed when a test is converted¶
Every converted action other than the main flow that uses a linked function
library starts with a generated block marked # _phoenix_library_binding,
which loads the library itself on every iteration (the reason is in
supported_versions.md). In an ALM conversion the main
flow instead gets a # _phoenix_library_scope block, which tells the actions
it runs which library to load.
Both blocks record the library the test was linked to when it was
converted: its ALM resource ID (for example _phx_want = '<resource id>') and
its file name, or, for a filesystem conversion, its absolute path. The values
come from each test's own links — they are not built into Phoenix — but they are
written into the script as literals. Nothing updates them when a library link
changes afterwards.
Changing the content of the linked library is safe: its resource ID and path
stay the same, so the block loads the new content. Changing which library is
linked is not. The block keeps loading the originally linked library for as long
as a copy of it can be found — in an ALM run, in UFT's extraction cache on the
execution host (%LOCALAPPDATA%\Temp\TD_80); for a filesystem conversion, at
its original path. It loads that library after UFT has bound the new one, so
the old library's functions replace any with the same name. No step fails and
nothing is reported.
| After conversion, you… | The converted test… |
|---|---|
| edit or replace the content of the linked library | runs the new content. Not affected. |
| link a different library with the same file name (for example, a copy from another folder) | runs the old library if a copy of it is in the cache — left by an earlier run, or extracted in the same run for another test that still links it. If none is, it uses the new library when that is the only one of that name in the cache, and otherwise leaves the choice to UFT. |
| link a library with a different file name, or add a second library | does not load the new library itself, so it has the problem the block exists to fix: in a multi-test Test Lab session the new library's functions can be missing from the second iteration onward. If a copy of the old library is still found, that is loaded too and its same-named functions win. |
| remove the link | still loads the old library while a copy can be found, which hides the removal. |
For a filesystem conversion the rule is simpler: the old library is loaded whenever its file still exists at the original path.
Relinking a test that calls other tests' actions changes those actions too: when run from that test, they load the library its main flow names.
Editing the generated lines by hand does not fix this. An action's own
_phx_want is overridden by the ID the main flow publishes, and the next
conversion rewrites both blocks.
Workaround:
- Change library content, not links, where you can. To give converted
tests different library code, update the library they already link: upload
new content into the same ALM Function library resource, or overwrite the
same
.pflon disk. Leave the_phoenix_lib_marker__…function at the end of the library in place; the block uses it to recognise the right library when it has to search by file name. - If a link must change, convert again afterwards. Conversion removes both generated blocks and writes them again from the test's links at that moment.
- ALM: Phoenix reads a test's links from its ALM resource relations (the Test Resources the test uses). Before you convert, check in ALM that the relation names the new library.
- ALM: conversion is whole-project and needs a current analysis in the
same run folder, so choose a new run ID and run the analysis under it
first, then convert. In the GUI, change Analysis Run ID on Step 5 — it
defaults to
<Domain>-<Project>, the same value on every run; on the CLI, pass--run-id. A conversion under an earlier run's ID resumes that run and skips every test it already converted that is still Python on the server, including the test you relinked, and re-running the analysis under that same ID does not clear the record. - ALM, if a resumed run stops on a cross-test reference: a test carried
forward from an interrupted run still serves as a callee, so its callers
convert as usual. The exception is a run folder whose journal an earlier
version of Phoenix wrote, before the converted action layout was recorded:
a caller of a test carried forward from it is blocked with "cross-test
reference … that callee has no conversion record in this run", and the
callee's entry in
alm_aom_results.jsonshowsresumed: trueand a note that its journal record carries no conversion maps. Re-running the conversion in that run folder cannot get past it and re-running Analysis there does not help; start over under a new run ID as above. The same message also appears when a callee failed or was not converted in the run; see troubleshooting.md. Keep the old run folder — the snapshots in itsalm_aom_work\alm_rollbackdirectory are the only pre-conversion originals of the tests converted before the interruption. - Filesystem: convert the test again; its links are read through UFT.
- Clear the cache on every execution host before the next run, using the command in advanced_troubleshooting.md §1c, so no old copy is left to find. On its own this is not enough if another test in the same run still links the old library, because UFT extracts it again for that test.
ALM environment boundaries¶
- Copy/paste-derived tests are refused (
shared-assets): ALM clipboard copies share the source test's asset ownership and cannot be converted in place. See advanced_troubleshooting.md §1a. - Libraries attached only through Test Settings (no ALM resource link) with bare function calls are not detectable — verify library linkage in ALM before migrating.
- OTA entity contracts vary by ALM deployment and patch level; site-specific behaviors encountered so far are catalogued in advanced_troubleshooting.md §6.
- UFT API/Service tests (C#-based) are inventoried and skipped; conversion coverage is GUI/script assets.
Asset locks¶
A lock never fails a conversion — locked assets are converted and
overwritten, and there is no locked failure status on the conversion
path. This 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); it blocks only entity metadata
writes (SetField/Post), which the upload path already treats as
optional. 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.
It has to: a whole-project ALM conversion is all-or-nothing, and a run that
dies partway leaves the project half Python and half VBScript, breaking
cross-test action chains and poisoning every later conversion.
The limitation is in the release, not the conversion. UnLockObject()
releases only your own lock — against another session's it returns cleanly
and silently does nothing, so the pipeline never trusts it and always
re-probes. Revoking a foreign lock means deleting its row from the
project's LOCKS table through the OTA Command object, which ALM
disables by default — most customers will not have it. Where it is
unavailable the conversion still completes and overwrites. The run records
entity-lock-force-revoked (revoked) or entity-lock-not-cleared-proceeding
(overwrote anyway), and each affected asset carries a note naming the holder.
The machine, ALM session id, lock time and last-active time appear only where
the LOCKS table is readable through the OTA Command object — the same
surface the revoke needs; without it the note names the user alone.
Overwriting takes the work of anyone still editing a locked asset, and their ALM client may hold a stale copy afterwards — run conversions in an agreed window with the project empty. See alm_safety.md § The safety model, item 9 (locks), for the full policy.
Scanner risk flags (review, not blockers)¶
Local filesystem analysis only. Its scan flags elevated-risk feature areas
for human review: Keyword View dependencies, Active Screen dependencies,
recovery scenarios, Business Process Testing indicators, and interactive input
methods. These are warnings; they do not change the verdict. ALM project
analysis raises none of them, and the recovery flag in particular fires only
when a script line names a .qrs file and contains the word recovery — see
the recovery entry above.
Supported Windows COM and WMI patterns¶
CreateObject/GetObject convert through a case-insensitive COM dispatch
wrapper. Validated patterns:
GetObject("winmgmts:"),InstancesOf("Win32_Process"),ExecQuery(...),For Eachover WMI collections,Process.Name,Process.ProcessId, collection.count,objProcess.Terminate()StrComp(left, right, vbBinaryCompare|vbTextCompare)
More complex WMI namespaces or broader COM object models should be reviewed during sign-off.
FileSystemObject coverage and restrictions¶
CreateObject("Scripting.FileSystemObject") converts to a COM-backed
Python equivalent. The converter handles the FileSystemObject and TextStream
members explicitly, including OpenTextFile, CreateTextFile,
OpenAsTextStream, Read, ReadAll, ReadLine, Skip, SkipLine, Write,
WriteLine, WriteBlankLines, Close, FileExists, FolderExists,
CreateFolder, DeleteFile, DeleteFolder, CopyFile, CopyFolder,
MoveFile, MoveFolder, GetFile, GetFolder, GetSpecialFolder,
BuildPath, GetTempName and GetAbsolutePathName.
Restrictions:
- Local Windows filesystem semantics are assumed; UNC, locked-file, and
permission-sensitive flows need runtime validation.
- Encoding, sharing, and line-ending behavior should be validated for
scripts depending on non-default text handling.
- The property-heavy object graphs GetFile/GetFolder return, binary
workflows, stream seeking, and ADODB-based file handling remain outside the
qualified path: they convert, but no run has validated them.
OptionalStep handling¶
OptionalStep.<object>.<action> converts to an existence guard around the
action — if <object>.exist(): <action> — with an else branch that reports
"Optional step skipped" as an informational (micDone) node, so a skipped
optional step still leaves evidence in the run report.
exist() is called with no argument, which waits the test's object
synchronization timeout — the same wait VBScript's OptionalStep performs.
This matters: an optional step is commonly used to dismiss a dialog or click a
control that takes a moment to render, and the step is meant to run if the
object turns up inside that window. An instantaneous exist(0) probe would
skip it and still pass, turning a wait-then-act step into a silent no-op.
Earlier Phoenix builds emitted exist(0), and a test converted by one of them
keeps it: re-converting a test preserves an action that is already Python as it
stands. To find them, search converted action scripts for .exist(0): followed
by an else branch that reports "Optional step skipped". To fix one, change
.exist(0) to .exist() in the script, or restore the test to VBScript
(uft-migrate restore, see alm_safety.md) and convert it
again; a filesystem conversion can simply be run again from the VBScript source.
In the reference estate the affected tests finished in 1–9 s against 22–26 s
under VBScript, the difference being exactly the wait that had been dropped.
Runtime parity¶
- Local UFT execution is not a Phoenix feature.
--run-uftwas removed when the AOM conversion path became canonical. The flag is still accepted purely so it can be refused by name — it fails the command (exit 2) rather than silently running nothing. To check a converted test locally, open it in UFT One and run it by hand. - ALM Test Lab execution (CLI-only support tooling,
--run-via-alm; the GUI never runs Test Lab) requires an active interactive desktop session on the execution host (GUI identification fails on locked/disconnected sessions) and report artifacts can vary by ALM/UFT patch level. - A "Passed" Test Lab run whose report is missing expected actions is
flagged
incomplete-executionrather than trusted.
Conversion can surface object-identification failures your baseline hid¶
This is the one result most likely to be mistaken for a regression, so read it before you compare a converted run against its baseline.
A test whose object descriptors no longer match the application can pass under VBScript and fail after conversion, having changed in no other way. The failure is not introduced by the conversion — it was already there, graded as a warning.
Measured on the reference estate, three tests failed after conversion against an 84/84 VBScript baseline. Their baseline runs look like this next to their passing near-duplicates:
| Duration | Final step | Graded | |
|---|---|---|---|
| the three affected tests | 24–27 s | Replay Warning |
Passed |
| their passing twins | 2–4 s | Verification |
Passed |
24–27 seconds is the object sync timeout (ObjectSyncTimeOut, 20,000 ms
by default) plus overhead. Those runs sat waiting for an object they could not
find, gave up, logged a Replay Warning — and UFT still graded the run
Passed. After conversion the same condition is booked as a Replay
Error, which fails the run. The underlying cause was stale identification
data: the tests carried vendor-era window titles that no longer match the
installed application.
How to find these before you convert. In your baseline results, look for
runs whose duration is at or just above your object sync timeout and whose
step list ends in a Replay Warning on a step that is not an
OptionalStep, with no checkpoint or verification step. A test that genuinely
exercises its application finishes far faster and books a real verification.
Fix the descriptors first and your baseline becomes a true baseline.
Do not over-read this. It is not a general rule that the Python engine is
stricter. Other tests in the same estate logged a Replay Warning at a similar
duration under VBScript and still passed after conversion. Those warnings came
from OptionalStep lines: under VBScript a skipped optional step is recorded as
a Replay Warning after the sync timeout has been waited out, which is intended
behaviour (see OptionalStep handling above). The tests that failed logged
their Replay Warning on a mandatory step. The grading difference itself is UFT
engine behaviour that Phoenix does not control; what is actionable is the
detection method above.
A source helper that scans every process runs slower after conversion¶
A VBScript helper that walks the whole process table — a "close process by
name" helper that calls
GetObject("winmgmts:").InstancesOf("Win32_Process") and reads .Name on every
returned object — adds seconds to every call once converted. Nothing fails and
no step result changes; the run is simply slower.
Measured on the reference estate, the same 84 tests on the same host, VBScript
baseline against the converted estate: the six tests that call such a helper
once or twice each ran 6–15 s slower per run (+15 s, +12 s, +10 s, +8 s,
+7 s, +6 s), roughly 4–12 s per call, where their whole VBScript runs took
1–8 s. The added time sits between the run's Start Action node and its first
reported step, and the cost grows with the number of processes on the machine.
Two controls locate the cost in the loop rather than anywhere else: tests that
link the same function library but never call the helper showed no change
(so it is not library loading), and tests calling a sibling copy of the helper
that asks WMI for the process by name —
ExecQuery("Select ... from Win32_Process where Name = '<exe>'") — showed no
change either. The cost therefore sits in the enumeration: one property read
across a COM boundary for every process on the machine.
The conversion is faithful: it carries whichever form the source library has. If the run time matters, change the source helper to the filtered query before converting, and the cost disappears.
Version-controlled ALM projects¶
Version-control support is implemented and needs no configuration:
versioning is detected automatically per asset; a pre-existing
checkout — another user's, or a stale one of ours — is undone first,
reverting the server working copy to the latest checked-in version (which is
what gets converted); check-in happens only after post-upload verification
passes; any failure after check-out abandons the checkout so the server
keeps its last checked-in version; and Python function-library resources
(.pfl) are checked out and checked in too, though each is checked in as
soon as its content lands — before the test's own verification — so a
failed test or an abort rollback does not revert a replaced library (its
previous version stays in ALM version history). Undoing another user's
checkout requires the connected ALM user to hold the "Manage
checkouts"-equivalent permission — without it the asset fails as "Version
control blocked" (vc-blocked).
The basic path — check out, convert, verify, check in as a new version — has
been run against a live version-enabled project, for tests without linked
function libraries. Undoing another user's checkout, the missing-permission
vc-blocked failure, abandon-on-failure, and check-out/check-in of function
library (.pfl) resources including PhoenixVBRuntime.pfl have unit coverage
only. Validate them on a copy of a version-enabled project before a
production wave.