Skip to content

Export variables inside strings as valid code - #1998

Merged
diemol merged 2 commits into
trunkfrom
fix-export-string-variables
Oct 29, 2026
Merged

diemol merged 2 commits into
trunkfrom
fix-export-string-variables

Conversation

@diemol

@diemol diemol commented Oct 29, 2026

Copy link
Copy Markdown
Member

Description

  • defaultPreprocessor marks each ${name} (all of them, not only the first) instead of putting the lookup in place.
  • After a command is emitted, resolveVariables replaces the marks:
    • inside a string literal, the string is split: "Hi ${name}!" becomes "Hi " + vars["name"] + "!"
    • a variable that is the whole string is used as it is: By.id(vars["ID"])
    • outside a string, it's the plain lookup
  • Each language passes variableInString: which quotes start a string, and how to turn a variable into a string (str() in Python, .to_s in Ruby).
  • Removed the echo workarounds that only handled a message that was just a variable.

Motivation and Context

A variable inside a string was exported as broken code, e.g. console.log("This is a test string vars["number"]") or By.id("vars["ID"]"). This came from v3.

Exporting every project in tests/examples in all six languages:

  • JavaScript: 3 of 18 files didn't compile before, 0 now.
  • Ruby: 2 of 18 before, 0 now.
  • Python: 2 of 18 before, 1 now (an empty times loop, a separate bug).
  • Java and C#: the same lines are fixed (checked by reading the output).

Types of changes

  • Bug fix (non-breaking change which fixes an issue)
  • New feature (non-breaking change which adds functionality)
  • Breaking change (fix or feature that would cause existing functionality to change)

Checklist

  • I have read the contributing document.
  • My change requires a change to the documentation.
  • I have updated the documentation accordingly.
  • I have added tests to cover my changes.
  • All new and existing tests passed.

🤖 Generated with Claude Code

The preprocessor marks each ${name} in a target or value, and the marks are
replaced after the command is emitted. Inside a string literal, the string
is split around the variable ("Hi " + vars["name"]). A variable that is the
whole string is used as it is. Outside a string, it's the plain lookup.

Each language says which quotes start a string and how to turn a variable
into a string (str() in Python, .to_s in Ruby).

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
@diemol

diemol commented Oct 29, 2026

Copy link
Copy Markdown
Member Author

/agentic_review

@qodo-code-review

Copy link
Copy Markdown

PR Summary by Qodo

Export variables inside strings as valid code

🐞 Bug fix 🧪 Tests 🕐 20-40 Minutes

Grey Divider

AI Description

• Resolve every variable after command emission so string literals produce valid code.
• Apply language-specific quoting and string conversion across JavaScript, Python, Ruby, Java, and
 C# exporters.
• Remove echo-specific workarounds and test mixed text, multiple variables, and escaped quotes.
Diagram

graph TD
  A["Command parameters"] --> B["Mark variables"] --> C["Language emitter"] --> D["Resolve variables"] --> E["Exported code"]
  F["Language options"] --> D
Loading
High-Level Assessment

The following are alternative approaches to this PR:

1. Resolve variables within each language emitter
  • ➕ Each exporter can handle its own string syntax directly.
  • ➖ Duplicates interpolation logic across exporters and risks inconsistent behavior between commands.

Recommendation: Keep deferred resolution in the shared export pipeline: it fixes both whole-string and embedded variables without duplicating logic across emitters. Review the quote-scanning behavior carefully, since it must interpret emitted source from several languages.

Files changed (10) +218 / -37

Bug fix (8) +176 / -32
command.tsConfigure C# string interpolation for shared resolution +9/-2

Configure C# string interpolation for shared resolution

• Passes C# quote and conversion rules into command emission and exports them for other C# exporters. Removes echo’s special-case variable handling.

packages/code-export-csharp-commons/src/command.ts

command.tsPass C# string rules to xUnit command emission +1/-0

Pass C# string rules to xUnit command emission

• Reuses the C# commons string-variable options when emitting xUnit commands.

packages/code-export-csharp-xunit/src/command.ts

command.tsConfigure Java string resolution and simplify echo +8/-2

Configure Java string resolution and simplify echo

• Supplies Java’s quote and conversion rules to the shared emitter. Leaves echo quoting to the emitter and variable resolution to the shared pipeline.

packages/code-export-java-junit/src/command.ts

command.tsConfigure JavaScript string resolution and simplify echo +8/-2

Configure JavaScript string resolution and simplify echo

• Recognizes double quotes, single quotes, and backticks during variable resolution. Removes echo’s special handling for messages beginning with a variable lookup.

packages/code-export-javascript-mocha/src/command.ts

command.tsUse shared variable resolution for Python strings +8/-14

Use shared variable resolution for Python strings

• Configures Python’s supported quotes and str() conversion. Removes the echo-only formatting helper in favor of shared resolution.

packages/code-export-python-pytest/src/command.ts

command.tsConfigure Ruby string resolution and simplify echo +8/-2

Configure Ruby string resolution and simplify echo

• Supplies Ruby quote rules and .to_s conversion for variables joined with text. Removes echo’s special-case variable lookup handling.

packages/code-export-ruby-rspec/src/command.ts

emit.tsResolve marked variables after command emission +52/-4

Resolve marked variables after command emission

• Adds optional language string rules to emission context and resolves variables in strings, statements, and nested command groups. Applies resolution before window-handling output in both emission paths.

packages/side-code-export/src/code-export/emit.ts

preprocessor.tsMark all variables and resolve them in emitted source +82/-6

Mark all variables and resolve them in emitted source

• Changes default preprocessing to mark every ${name} reference instead of immediately inserting the first lookup. Adds a resolver that distinguishes variables inside and outside string literals and joins embedded values using language-specific conversion.

packages/side-code-export/src/code-export/preprocessor.ts

Tests (2) +42 / -5
command.spec.js.snapUpdate Python echo snapshot for string concatenation +1/-1

Update Python echo snapshot for string concatenation

• Changes the expected variable-containing echo output from format substitution to concatenation with str().

packages/code-export-python-pytest/test/src/snapshots/command.spec.js.snap

preprocessor.spec.tsTest variable resolution across string contexts +41/-4

Test variable resolution across string contexts

• Tests embedded and whole-string variables, consecutive variables, expressions outside strings, and escaped quotes. Adjusts existing default and key-preprocessor expectations to resolve marked variables before comparison.

packages/side-code-export/test/src/code-export/preprocessor.spec.ts

@qodo-code-review

qodo-code-review Bot commented Oct 29, 2026 •

Copy link
Copy Markdown

Code Review by Qodo

🐞 Bugs (0) 📘 Rule violations (0) 📜 Skill insights (0)

Grey Divider


Action required

1. Ruby exports can execute host commands ✓ Resolved
Description
resolveVariables accepts a marker pair already present in an asynchronous script target and passes
its contents to Ruby's variableLookup without validating the name. Because that emitter places the
script in a double-quoted Ruby string without escaping single quotes, a crafted marker name can
close the lookup expression and append a host command to the exported test.
Code

packages/side-code-export/src/code-export/preprocessor.ts[R109-112]

+    if (character === VARIABLE_START) {
+      const end = code.indexOf(VARIABLE_END, i)
+      const lookup = variableLookup(code.slice(i + 1, end))
+      i = end
Evidence
escapeString changes double quotes rather than escaping a script-preprocessed parameter, and
scriptPreprocessor preserves a raw marker pair. The Ruby asynchronous-script emitter embeds that
script inside double quotes; the new resolver then constructs @vars['${varName}'] from the
unvalidated marker contents. With an initialized @vars['x'], a marker containing `x']);
system('id'); # yields a statement ending in + @vars['x']); system('id'); #`, so the exported Ruby
executes system after the script call.

packages/side-code-export/src/code-export/preprocessor.ts[38-46]
packages/side-code-export/src/code-export/preprocessor.ts[95-124]
packages/side-code-export/src/code-export/preprocessor.ts[151-182]
packages/code-export-ruby-rspec/src/command.ts[153-155]
packages/code-export-ruby-rspec/src/command.ts[423-429]

Agent prompt
The issue below was found during a code review. Follow the provided context and guidance below and implement a solution

## Issue description
The resolver treats user-supplied private-use characters as variable markers. In Ruby asynchronous-script exports, a marker name containing a single quote can inject Ruby code into the generated test.
## Fix Focus Areas
- packages/side-code-export/src/code-export/preprocessor.ts[62-75]
- packages/side-code-export/src/code-export/preprocessor.ts[95-115]
## Recommended Fix
Keep user-supplied marker characters literal or escape them before preprocessing, and resolve only markers created from validated `${name}` tokens. Add an asynchronous Ruby export regression test with a crafted marker pair.

ⓘ Copy this prompt and use it to remediate the issue with your preferred AI generation tools



Remediation recommended

2. JavaScript adds numbers instead of joining text ✓ Resolved
Description
The JavaScript variableInString.toString is the identity and the lookup is a bare vars["x"], so
"${a}${b} items" becomes vars["a"] + vars["b"] + " items". When both variables hold numbers
(e.g. from storeXpathCount or executeScript), JavaScript adds them first, so 1 and 2 give
"3 items" instead of "12 items", and a bare "${a}${b}" gives a number.
Code

packages/code-export-javascript-mocha/src/command.ts[R149-152]

+const variableInString = {
+  quotes: ['"', "'", '`'],
+  toString: (lookup: string) => lookup,
+}
Evidence
When a string starts with a variable, resolveVariables drops the opening quote (`if (startsString)
result = result.slice(0, -1)`). For two variables in a row, nothing is left on the left side to
force string concatenation. + runs left to right, so two numbers are added before any string
operand appears.

packages/side-code-export/src/code-export/preprocessor.ts[117-133]
packages/code-export-javascript-mocha/src/command.ts[154-156]

Agent prompt
The issue below was found during a code review. Follow the provided context and guidance below and implement a solution

## Issue description
JS `variableInString.toString` is the identity, so adjacent numeric variables at the start of a string are added together, not joined as text.

## Fix Focus Areas
- packages/code-export-javascript-mocha/src/command.ts[149-152]

## Recommended Fix
Set `toString: (lookup) => `String(${lookup})`` for JavaScript, then update the snapshots and tests.

ⓘ Copy this prompt and use it to remediate the issue with your preferred AI generation tools


3. Python and Ruby text checks fail against numeric variables ✓ Resolved
Description
resolveVariables skips toString when a variable fills the whole string literal (`startsString &&
endsString && !joined ? lookup : ...), so Python emits a bare self.vars["n"]` and Ruby a bare
@vars['n']. The IDE runtime always turns ${n} into a string, so a test like `storeXpathCount →
n then verifyText | id=count | ${n} passes in the IDE, but the exported pytest compares "3" ==
3` (False) and the RSpec version fails the same way.
Code

packages/side-code-export/src/code-export/preprocessor.ts[R122-124]

+      // A variable that is the whole string is used as it is
+      result +=
+        startsString && endsString && !joined ? lookup : toString(lookup)
Evidence
Python's storeXpathCount stores an int (len(...)). verifyText puts the value inside "...", so
${n} makes the whole literal a variable and it is left unconverted. Java and C# are not affected
because their lookups already end in .toString()/.ToString(), which shows the intended meaning
is a string. Python and Ruby pass str()/.to_s as toString, but it is never applied in this
case.

packages/code-export-python-pytest/src/command.ts[694-699]
packages/code-export-python-pytest/src/command.ts[851-860]
packages/code-export-python-pytest/src/command.ts[148-156]

Agent prompt
The issue below was found during a code review. Follow the provided context and guidance below and implement a solution

## Issue description
When a variable is the whole string literal, `resolveVariables` emits the raw lookup without `toString`. In Python and Ruby, numeric variables then compare unequal to element text, unlike the IDE runtime, which always interpolates to a string.

## Fix Focus Areas
- packages/side-code-export/src/code-export/preprocessor.ts[117-125]
- packages/code-export-python-pytest/src/command.ts[148-152]
- packages/code-export-ruby-rspec/src/command.ts[147-151]

## Recommended Fix
Always apply `toString(lookup)` when the mark is inside a string literal (drop the `startsString && endsString && !joined` shortcut). Or add an option to `VariableInStringOptions` (e.g. `stringifyWholeString: boolean`) and set it to true for Python and Ruby. Update the snapshots and the preprocessor tests to match.

ⓘ Copy this prompt and use it to remediate the issue with your preferred AI generation tools



Informational

4. Stray marker characters can hang the export ✓ Resolved
Description
resolveVariables uses code.indexOf(VARIABLE_END, i) without checking for -1, so an unmatched
U+E000 sets the scan index to -1 and repeatedly grows result from the start of the code. This
occurs when a command parameter contains a literal U+E000 that passes through preprocessing into
emitted code, or when a backslash in unescaped storeJson input causes the escape branch to consume
a start marker, preventing the export from finishing.
Code

packages/side-code-export/src/code-export/preprocessor.ts[R109-112]

+    if (character === VARIABLE_START) {
+      const end = code.indexOf(VARIABLE_END, i)
+      const lookup = variableLookup(code.slice(i + 1, end))
+      i = end
Evidence
defaultPreprocessor leaves ordinary characters untouched, and the echo emitter includes the
message in emitted code that the resolver then scans. The resolver recognizes raw marker characters
and does not guard against indexOf returning -1 for an unmatched U+E000; incrementing that index
restarts the scan at zero. Because storeJson skips escaping, a backslash before ${x} can also
make the escape branch consume the start marker and leave its end marker unmatched.

packages/side-code-export/src/code-export/preprocessor.ts[95-148]
packages/side-code-export/src/code-export/emit.ts[184-199]
packages/side-code-export/src/code-export/preprocessor.ts[67-75]
packages/code-export-python-pytest/src/command.ts[404-406]
packages/side-code-export/src/code-export/emit.ts[104-115]
packages/side-code-export/src/code-export/preprocessor.ts[107-112]

Agent prompt
The issue below was found during a code review. Follow the provided context and guidance below and implement a solution

## Issue description
An unmatched U+E000 in emitted code makes `resolveVariables` revisit the same text indefinitely. A backslash in unescaped `storeJson` input can also cause the escape branch to consume a variable start marker.

## Fix Focus Areas
- packages/side-code-export/src/code-export/preprocessor.ts[107-136]

## Recommended Fix
When no end marker exists, append the remaining code as literal text and exit the loop instead of setting the scan index to `-1`. In the backslash branch, do not consume the next character if it is `VARIABLE_START`. Add an export regression test with an unmatched U+E000 in an echo message. Optionally strip U+E000/U+E001 from parameters before marking.

ⓘ Copy this prompt and use it to remediate the issue with your preferred AI generation tools


5. Old variable-prefix checks in exporters never match now ✓ Resolved
Description
defaultPreprocessor and keysPreprocessor now return markers instead of vars[...] lookups, so
the startsWith('self.vars['), startsWith('vars['), startsWith('@vars[') and
startsWith('vars.get') branches in every language's send-keys helper and in Python's emitLabel
can never be true. A later reader will assume these branches handle variables, while the real
handling has moved into resolveVariables.
Code

packages/side-code-export/src/code-export/preprocessor.ts[R72-75]

+  return param.replace(
+    /\$\{(\w+)\}/g,
+    (_match, name) => `${VARIABLE_START}${name}${VARIABLE_END}`
+  )
Evidence
The preprocessors now give emitters a marked name such as \uE000x\uE001, never a string starting
with the lookup prefix. Those branches are unreachable, the same way the PR already removed the
matching echo workarounds.

packages/code-export-python-pytest/src/selection.ts[43-48]
packages/code-export-python-pytest/src/command.ts[609-630]

Agent prompt
The issue below was found during a code review. Follow the provided context and guidance below and implement a solution

## Issue description
The variable-prefix checks in the send-keys helpers and the Python select label emitter can no longer match, because variables reach emitters as markers.

## Fix Focus Areas
- packages/code-export-python-pytest/src/selection.ts[43-48]
- packages/code-export-python-pytest/src/command.ts[609-630]
- packages/code-export-javascript-mocha/src/command.ts[652-672]
- packages/code-export-java-junit/src/command.ts[637-657]
- packages/code-export-csharp-commons/src/command.ts[650-670]
- packages/code-export-ruby-rspec/src/command.ts[597-617]

## Recommended Fix
Remove the `startsWith(<lookup prefix>)` branches, the same way the `echo` workarounds were removed, so values are always quoted and `resolveVariables` handles them.

ⓘ Copy this prompt and use it to remediate the issue with your preferred AI generation tools


Grey Divider

Context sources
Review mode: Auto: 🧠 Deep: Complex string-resolution logic spans multiple language exporters and edge cases.
✅ Fix-loop round: fix loop

Grey Divider

Tip of the day
💡 Did you know, you can add REVIEW.md to your repo root and Qodo follows it on every PR

More tips ↗ | Customize Qodo ↗ | Qodo docs ↗

Grey Divider

Previous reviews

Review updated until commit 5832489

Results up to commit 00c5db2 🧠 Deep


🐞 Bugs (0) 📘 Rule violations (0) 📎 Requirement gaps (0) 🎨 UX issues (0) 🔗 Cross-repo conflicts (0) 📜 Skill insights (0)


Action required
1. Ruby exports can execute host commands ✓ Resolved
Description
resolveVariables accepts a marker pair already present in an asynchronous script target and passes
its contents to Ruby's variableLookup without validating the name. Because that emitter places the
script in a double-quoted Ruby string without escaping single quotes, a crafted marker name can
close the lookup expression and append a host command to the exported test.
Code

packages/side-code-export/src/code-export/preprocessor.ts[R109-112]

+    if (character === VARIABLE_START) {
+      const end = code.indexOf(VARIABLE_END, i)
+      const lookup = variableLookup(code.slice(i + 1, end))
+      i = end
Evidence
escapeString changes double quotes rather than escaping a script-preprocessed parameter, and
scriptPreprocessor preserves a raw marker pair. The Ruby asynchronous-script emitter embeds that
script inside double quotes; the new resolver then constructs @vars['${varName}'] from the
unvalidated marker contents. With an initialized @vars['x'], a marker containing `x']);
system('id'); # yields a statement ending in + @vars['x']); system('id'); #`, so the exported Ruby
executes system after the script call.

packages/side-code-export/src/code-export/preprocessor.ts[38-46]
packages/side-code-export/src/code-export/preprocessor.ts[95-124]
packages/side-code-export/src/code-export/preprocessor.ts[151-182]
packages/code-export-ruby-rspec/src/command.ts[153-155]
packages/code-export-ruby-rspec/src/command.ts[423-429]

Agent prompt
The issue below was found during a code review. Follow the provided context and guidance below and implement a solution

## Issue description
The resolver treats user-supplied private-use characters as variable markers. In Ruby asynchronous-script exports, a marker name containing a single quote can inject Ruby code into the generated test.
## Fix Focus Areas
- packages/side-code-export/src/code-export/preprocessor.ts[62-75]
- packages/side-code-export/src/code-export/preprocessor.ts[95-115]
## Recommended Fix
Keep user-supplied marker characters literal or escape them before preprocessing, and resolve only markers created from validated `${name}` tokens. Add an asynchronous Ruby export regression test with a crafted marker pair.

ⓘ Copy this prompt and use it to remediate the issue with your preferred AI generation tools



Remediation recommended
2. JavaScript adds numbers instead of joining text ✓ Resolved
Description
The JavaScript variableInString.toString is the identity and the lookup is a bare vars["x"], so
"${a}${b} items" becomes vars["a"] + vars["b"] + " items". When both variables hold numbers
(e.g. from storeXpathCount or executeScript), JavaScript adds them first, so 1 and 2 give
"3 items" instead of "12 items", and a bare "${a}${b}" gives a number.
Code

packages/code-export-javascript-mocha/src/command.ts[R149-152]

+const variableInString = {
+  quotes: ['"', "'", '`'],
+  toString: (lookup: string) => lookup,
+}
Evidence
When a string starts with a variable, resolveVariables drops the opening quote (`if (startsString)
result = result.slice(0, -1)`). For two variables in a row, nothing is left on the left side to
force string concatenation. + runs left to right, so two numbers are added before any string
operand appears.

packages/side-code-export/src/code-export/preprocessor.ts[117-133]
packages/code-export-javascript-mocha/src/command.ts[154-156]

Agent prompt
The issue below was found during a code review. Follow the provided context and guidance below and implement a solution

## Issue description
JS `variableInString.toString` is the identity, so adjacent numeric variables at the start of a string are added together, not joined as text.

## Fix Focus Areas
- packages/code-export-javascript-mocha/src/command.ts[149-152]

## Recommended Fix
Set `toString: (lookup) => `String(${lookup})`` for JavaScript, then update the snapshots and tests.

ⓘ Copy this prompt and use it to remediate the issue with your preferred AI generation tools


3. Python and Ruby text checks fail against numeric variables ✓ Resolved
Description
resolveVariables skips toString when a variable fills the whole string literal (`startsString &&
endsString && !joined ? lookup : ...), so Python emits a bare self.vars["n"]` and Ruby a bare
@vars['n']. The IDE runtime always turns ${n} into a string, so a test like `storeXpathCount →
n then verifyText | id=count | ${n} passes in the IDE, but the exported pytest compares "3" ==
3` (False) and the RSpec version fails the same way.
Code

packages/side-code-export/src/code-export/preprocessor.ts[R122-124]

+      // A variable that is the whole string is used as it is
+      result +=
+        startsString && endsString && !joined ? lookup : toString(lookup)
Evidence
Python's storeXpathCount stores an int (len(...)). verifyText puts the value inside "...", so
${n} makes the whole literal a variable and it is left unconverted. Java and C# are not affected
because their lookups already end in .toString()/.ToString(), which shows the intended meaning
is a string. Python and Ruby pass str()/.to_s as toString, but it is never applied in this
case.

packages/code-export-python-pytest/src/command.ts[694-699]
packages/code-export-python-pytest/src/command.ts[851-860]
packages/code-export-python-pytest/src/command.ts[148-156]

Agent prompt
The issue below was found during a code review. Follow the provided context and guidance below and implement a solution

## Issue description
When a variable is the whole string literal, `resolveVariables` emits the raw lookup without `toString`. In Python and Ruby, numeric variables then compare unequal to element text, unlike the IDE runtime, which always interpolates to a string.

## Fix Focus Areas
- packages/side-code-export/src/code-export/preprocessor.ts[117-125]
- packages/code-export-python-pytest/src/command.ts[148-152]
- packages/code-export-ruby-rspec/src/command.ts[147-151]

## Recommended Fix
Always apply `toString(lookup)` when the mark is inside a string literal (drop the `startsString && endsString && !joined` shortcut). Or add an option to `VariableInStringOptions` (e.g. `stringifyWholeString: boolean`) and set it to true for Python and Ruby. Update the snapshots and the preprocessor tests to match.

ⓘ Copy this prompt and use it to remediate the issue with your preferred AI generation tools



Informational
4. Stray marker characters can hang the export ✓ Resolved
Description
resolveVariables uses code.indexOf(VARIABLE_END, i) without checking for -1, so an unmatched
U+E000 sets the scan index to -1 and repeatedly grows result from the start of the code. This
occurs when a command parameter contains a literal U+E000 that passes through preprocessing into
emitted code, or when a backslash in unescaped storeJson input causes the escape branch to consume
a start marker, preventing the export from finishing.
Code

packages/side-code-export/src/code-export/preprocessor.ts[R109-112]

+    if (character === VARIABLE_START) {
+      const end = code.indexOf(VARIABLE_END, i)
+      const lookup = variableLookup(code.slice(i + 1, end))
+      i = end
Evidence
defaultPreprocessor leaves ordinary characters untouched, and the echo emitter includes the
message in emitted code that the resolver then scans. The resolver recognizes raw marker characters
and does not guard against indexOf returning -1 for an unmatched U+E000; incrementing that index
restarts the scan at zero. Because storeJson skips escaping, a backslash before ${x} can also
make the escape branch consume the start marker and leave its end marker unmatched.

packages/side-code-export/src/code-export/preprocessor.ts[95-148]
packages/side-code-export/src/code-export/emit.ts[184-199]
packages/side-code-export/src/code-export/preprocessor.ts[67-75]
packages/code-export-python-pytest/src/command.ts[404-406]
packages/side-code-export/src/code-export/emit.ts[104-115]
packages/side-code-export/src/code-export/preprocessor.ts[107-112]

Agent prompt
The issue below was found during a code review. Follow the provided context and guidance below and implement a solution

## Issue description
An unmatched U+E000 in emitted code makes `resolveVariables` revisit the same text indefinitely. A backslash in unescaped `storeJson` input can also cause the escape branch to consume a variable start marker.

## Fix Focus Areas
- packages/side-code-export/src/code-export/preprocessor.ts[107-136]

## Recommended Fix
When no end marker exists, append the remaining code as literal text and exit the loop instead of setting the scan index to `-1`. In the backslash branch, do not consume the next character if it is `VARIABLE_START`. Add an export regression test with an unmatched U+E000 in an echo message. Optionally strip U+E000/U+E001 from parameters before marking.

ⓘ Copy this prompt and use it to remediate the issue with your preferred AI generation tools


5. Old variable-prefix checks in exporters never match now ✓ Resolved
Description
defaultPreprocessor and keysPreprocessor now return markers instead of vars[...] lookups, so
the startsWith('self.vars['), startsWith('vars['), startsWith('@vars[') and
startsWith('vars.get') branches in every language's send-keys helper and in Python's emitLabel
can never be true. A later reader will assume these branches handle variables, while the real
handling has moved into resolveVariables.
Code

packages/side-code-export/src/code-export/preprocessor.ts[R72-75]

+  return param.replace(
+    /\$\{(\w+)\}/g,
+    (_match, name) => `${VARIABLE_START}${name}${VARIABLE_END}`
+  )
Evidence
The preprocessors now give emitters a marked name such as \uE000x\uE001, never a string starting
with the lookup prefix. Those branches are unreachable, the same way the PR already removed the
matching echo workarounds.

packages/code-export-python-pytest/src/selection.ts[43-48]
packages/code-export-python-pytest/src/command.ts[609-630]

Agent prompt
The issue below was found during a code review. Follow the provided context and guidance below and implement a solution

## Issue description
The variable-prefix checks in the send-keys helpers and the Python select label emitter can no longer match, because variables reach emitters as markers.

## Fix Focus Areas
- packages/code-export-python-pytest/src/selection.ts[43-48]
- packages/code-export-python-pytest/src/command.ts[609-630]
- packages/code-export-javascript-mocha/src/command.ts[652-672]
- packages/code-export-java-junit/src/command.ts[637-657]
- packages/code-export-csharp-commons/src/command.ts[650-670]
- packages/code-export-ruby-rspec/src/command.ts[597-617]

## Recommended Fix
Remove the `startsWith(<lookup prefix>)` branches, the same way the `echo` workarounds were removed, so values are always quoted and `resolveVariables` handles them.

ⓘ Copy this prompt and use it to remediate the issue with your preferred AI generation tools


Grey Divider

Qodo Logo

- Remove marker characters from command parameters, so only marks made
  from ${name} are resolved
- Keep a mark with no end as it is, and don't let a backslash consume a mark
- Always convert a variable to a string inside a string literal, and use
  String() in JavaScript so numbers are joined, not added
- Remove the variable checks in send keys and the Python label locator,
  which can't match anymore

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
@diemol

diemol commented Oct 29, 2026

Copy link
Copy Markdown
Member Author

/agentic_review

@qodo-code-review

Copy link
Copy Markdown

Code review by qodo was updated up to the latest commit 5832489

@diemol
diemol merged commit 28bb205 into trunk Oct 29, 2026
5 checks passed
@diemol
diemol deleted the fix-export-string-variables branch October 29, 2026 22:31
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant