Migrating a LoadRunner Script to OctoPerf With an AI Agent
A VuGen script is C. Some scripts are a recorded journey with helpers around it, others are a framework that happens to send a few requests. OctoPerf 17 ships a LoadRunner migration playbook for AI agents whose first job is to tell you which one you have.
Real prompts, real output, script downloadable.
Table of Contents¶
- The Script
- Step 1: Survey
- Step 2: Import
- Step 3: Work the Unconverted List
- Step 4: Validate
- What This Saves
- Conclusion
The Script¶
Target: JPetStore, our public MyBatis demo shop. Every step is reproducible.

One Action.c, HTTP recording level, eight transactions walking a purchase. Around them:
- hand-written C computing which item the user buys,
- a condition reading a C local, guarding a fallback request,
- a parameterised server,
- a recorded cookie, re-sent after the cookie jar is emptied,
.prmparameters backed by.datfiles.

It replays green in VuGen. Download it to follow along.
Step 1: Survey¶
The first prompt is not an import.
Here is a LoadRunner script folder. Before importing anything, tell me whether it is worth migrating.
Nothing is created. Trimmed answer:
"portability": "ASSISTED",
"protocols": [ { "protocol": "QTWeb", "scripts": 1, "convertible": true } ],
"inventory": {
"calls": 100, "pages": 8, "transactions": 8, "actions": 3,
"extractors": 3, "assertions": 3, "parameters": 3,
"dataFiles": 2, "unsupportedCalls": 3
}
Three verdicts:
MECHANICAL, plain HTTP, runs nearly as it is,ASSISTED, HTTP plus C helpers or control flow, import and work the list,MOSTLY_MANUAL, the C outweighs the requests.
MOSTLY_MANUAL is not a difficulty rating, it is a different script. Plenty of authors built a whole framework in a .c: their calls are getenv, sprintf, strtok and their own helpers. Nothing there converts, because nothing there is a request.
Two habits. A script can declare QTWeb and send nothing at all, so read the inventory, not the protocol. And read the per-script breakdown: an upload can be MOSTLY_MANUAL overall while three of its six scripts are plain HTTP.
Step 2: Import¶
Import it into a new project.

Transactions became named containers, web_reg_find became assertions, .prm parameters became variables with their .dat uploaded, the parameterised server became https://${host}:443, and the recorded cookie became a Cookie header.
Zip the whole folder, not the Action.c. data/ is not optional: an HTML-level recording carries no URLs of its own, so without its snapshots every click imports as a request against the entry page. Drop result*/, data/http.db/ and the replay logs.
The sanity check already flags two ContainerAction is empty: the markers the C left behind.
Step 3: Work the Unconverted List¶
Work the unconverted list. Explain each entry before you fix it.
Work from the list, not from the tree. Each entry carries the script, the action and the line of the .c, which is what makes it actionable in a two-thousand-line file.
The C That Computed a Value¶


Three entries, one story: rand line 300, sprintf line 301, the parameter they feed line 302. lr_save_string converts on its own, so what gets reported is the arithmetic feeding it.
Three markers, one Groovy action:
def items = ["EST-1", "EST-2"]
vars.put("itemId", items[new Random().nextInt(items.size())])
Other shapes of C: one that wrapped web calls produced no request, so rebuild or re-record. One that logged or counted is dropped silently. One that paused leaves you choosing the number, since the argument was a C local.
The Condition That Lost Its Guard¶

The steps are in the tree, the guard is not. The fallback sits inside its container, the original condition survives in the node name, but the container has no working guard until you give it one.
Three C shapes convert, all three reading a LoadRunner parameter:
strcmp(lr_eval_string("{status}"), "OK") == 0
atoi(lr_eval_string("{count}")) >= 1
strstr(lr_eval_string("{body}"), "token") != NULL
Everything else reads a C local. Ours is rc != LR_PASS, the guard you meet most often, and nothing on the OctoPerf side stands for "my helper failed". Say what you meant:
${__groovy(!prev.isSuccessful())}
Step 4: Validate¶
Validate the Virtual User.
Validation replays the Virtual User on a real load generator and consumes no credits.
The first run fails on the markers, and everything downstream fails with them: no item picker, no item page. One root cause with a tail.
After the list, the same Virtual User validates 32 out of 32.

The request count went down between the two runs: the fallback now fires only when the sign-in actually fails. The count dropped because the test became correct.
What This Saves¶
The agent invented nothing. Requests, transactions, headers, think times, parameters and data files converted mechanically.
What it added: knowing what to do with what did not convert, in the right order, and seeing that three entries were one story told three times.
The caveat: it depends entirely on the verdict. ASSISTED is where an agent shines. On a MOSTLY_MANUAL script, the useful answer comes from the survey in the first minute, and it is do not.
Conclusion¶
Four prompts: survey, import, work the list, validate. The script and JPetStore are public, so nothing here needs to be taken on trust.
Moving off LoadRunner? Run the survey on your worst script, not your best. It writes nothing, costs nothing, and the verdict is the only part of a migration plan that is hard to guess.
The NeoLoad walkthrough follows the same path, scenario included.