Skip to content

CONCEPT Cited by 1 source

DOM context injection for LLM

Definition

DOM context injection for LLM is the technique of capturing the actual rendered DOM of a UI component at test-run time and feeding it to an LLM as disambiguating context for a downstream task (typically code conversion or generation) that depends on what the DOM actually looks like — which the source code alone does not fully reveal.

It's a specialisation of the more general "give the LLM the runtime artifact, not just the source" technique: - For SQL-generation tasks, this would be sample rows or the actual table schema at query time. - For API-migration tasks, this would be a recorded request / response pair. - For front-end test migration, it's the rendered DOM per test case.

Why it matters for Enzyme → RTL migration

systems/react-testing-library queries the rendered DOM (getByRole, getByText, getByTestId etc.), while systems/enzyme queries the component tree (wrapper.find('ComponentName')). Picking the right RTL query for a given Enzyme call requires knowing:

  • Does the component render as an element with a detectable ARIA role? → getByRole(...)
  • Does it have a data-qa / data-testid attribute? → getByTestId(...)
  • Does it render visible text that's unique enough to query against? → getByText(...)

None of this is inferable from the test source file alone. The test file creates the component; the rendered DOM depends on the component's implementation, the props passed in, any feature flags, any conditional rendering based on props/state.

Slack's retrospective puts the problem cleanly:

"the choice between getByRole and getByTestId depended on the accessibility roles or test IDs present in the rendered component. However, AST lacks the capability to incorporate such contextual information. Its functionality is confined to processing the conversion logic based solely on the contents of the file being transformed, without consideration for external sources such as the actual DOM or React component code." (Source: sources/2024-06-19-slack-ai-powered-conversion-from-enzyme-to-react-testing-library)

If the AST can't see the DOM, neither can an LLM prompted only with the test source. The structural fix is to inject the DOM as context.

Mechanism (Slack's implementation)

Slack instruments Enzyme's own render methods to capture the rendered HTML per test case:

// Import original methods
import enzyme, { mount as originalMount, shallow as originalShallow } from 'enzyme';
import fs from 'fs';

let currentTestCaseName: string | null = null;

beforeEach(() => {
    // Set the current test case name before each test
    const testName = expect.getState().currentTestName;
    currentTestCaseName = testName ? testName.trim() : null;
});

afterEach(() => {
    // Reset the current test case name after each test
    currentTestCaseName = null;
});

// Override mount method
enzyme.mount = (node: React.ReactElement, options?: enzyme.MountRendererProps) => {
    const wrapper = originalMount(node, options);
    const htmlContent = wrapper.html();
    if (process.env.DOM_TREE_FILE) {
        fs.appendFileSync(
            process.env.DOM_TREE_FILE,
            `<test_case_title>${currentTestCaseName}</test_case_title> and <dom_tree>${htmlContent}</dom_tree>;\n`,
        );
    }
    return wrapper;
};
// ... same treatment for shallow

The output file contains one <test_case_title>...</test_case_title> and <dom_tree>...</dom_tree> pair per test case. The pipeline then injects these pairs into the LLM prompt inside <component> tags, so each test case the LLM converts has the exact DOM it was rendering against.

Why per-test-case, not per-component

A single test file typically contains many test cases (it('...', () => { ... })), each potentially passing different props or triggering different conditional rendering. Slack:

"This collection step was essential because each test case might have different setups and properties passed to the component, resulting in varying DOM structures for each test case."

Capturing DOM per-component would smear the signal across all test cases' DOMs; capturing per-test-case preserves the case-specific context the LLM needs.

Generalisation

The pattern — instrument the runtime, capture the artifact, inject it into the LLM prompt — generalises to:

  • Schema migration: capture live row samples per source table, inject into prompt for SQL rewrite.
  • API migration: capture recorded request/response pairs, inject into the prompt for client-code rewrite.
  • i18n migration: capture rendered strings at runtime, inject as the set of strings that need translation keys.
  • Config migration: capture the resolved config at runtime, inject alongside the raw config file.

The common shape: the source code is insufficient; the runtime state carries information the LLM needs.

Seen in

  • sources/2024-06-19-slack-ai-powered-conversion-from-enzyme-to-react-testing-library — Slack's Enzyme-to-RTL codemod captures per-test-case DOM via Enzyme render-method instrumentation, injects it as <component><test_case_title>...</test_case_title> and <dom_tree>...</dom_tree></component> blocks in the LLM prompt. A core component of why the hybrid AST + LLM pipeline reached ~80% conversion quality versus 40-60% for pure-LLM prompting.
Last updated · 470 distilled / 1,213 read