How to modify the fresh loaded/rendered content present in a <turbo-frame> element?

I’ve successfully implemented a search form (i.e. a text input field) that changes the src attribute value of a <turbo-frame> element (which one automatically fetches and renders data immediately after the change). The search result rendered in the <turbo-frame> is a <ul><li> list.

Now, I want to add a custom attribute (i.e. setAttribute()) to the <li> list elements each time after the search result is loaded/rendered.

I tried to use addEventListener() (on Stimulus controller, document and window with Turbo events and non-Turbo events), the built-in Stimulus function afterLoad and some other weird things (e.g. MutationObserver) without success.

How to modify the content present in the <turbo-frame> after it is freshly loaded/rendered?

Here is a Stimulus controller code example using event listeners:

// app/javascript/controllers/search_controller.js

import { Controller } from "@hotwired/stimulus";

// Connects to data-controller="search"
export default class extends Controller {
  static targets = ["field", "resultTurboFrame", "item"];

  connect() {

    // These event listeners do not fire at all:

    this.resultTurboFrameTarget.addEventListener("turbo:frame-load", this.resultHandler);
    document.addEventListener("turbo:frame-load", this.resultHandler);
    window.addEventListener("turbo:frame-load", this.resultHandler);

    this.resultTurboFrameTarget.addEventListener("turbo:frame-render", this.resultHandler);
    document.addEventListener("turbo:frame-render", this.resultHandler);
    window.addEventListener("turbo:frame-render", this.resultHandler);

    this.resultTurboFrameTarget.addEventListener("turbo:before-frame-render", this.resultHandler);
    document.addEventListener("turbo:before-frame-render", this.resultHandler);
    window.addEventListener("turbo:before-frame-render", this.resultHandler);

    this.resultTurboFrameTarget.addEventListener("turbo:before-render", this.resultHandler);
    document.addEventListener("turbo:before-render", this.resultHandler);
    window.addEventListener("turbo:before-render", this.resultHandler);


    // These event listeners fire but I cannot modify the fresh 
    // loaded/rendered content because event.target.querySelectorAll("li") 
    // returns an empty NodeList which length is 0 i.e. <li> elements are not recognized
    
    this.resultTurboFrameTarget.addEventListener("turbo:before-fetch-response", this.resultHandler);
    document.addEventListener("turbo:before-fetch-response", this.resultHandler);
    window.addEventListener("turbo:before-fetch-response", this.resultHandler);
    
    this.resultTurboFrameTarget.addEventListener("turbo:before-fetch-request", this.resultHandler);
    document.addEventListener("turbo:before-fetch-request", this.resultHandler);
    window.addEventListener("turbo:before-fetch-request", this.resultHandler);

    this.resultTurboFrameTarget.addEventListener("turbo:before-stream-render", this.resultHandler);
    document.addEventListener("turbo:before-stream-render", this.resultHandler);
    window.addEventListener("turbo:before-stream-render", this.resultHandler);


    // Other non-working attempts that fire but I cannot modify the fresh
    // for the above-mentioned reasons.

    this.boundedResultHandler = this.resultTurboFrameHandler.bind(this)
    this.searchResultFrameTarget.addEventListener("turbo:before-fetch-response", this.boundedResultHandler);
    document.addEventListener("turbo:before-fetch-response", this.boundedResultHandler);
    window.addEventListener("turbo:before-fetch-response", this.boundedResultHandler);

  }

  disconnect() {
    this.resultTarget.removeEventListener("turbo:frame-render", this.resultHandler);
    // ... 
  }

  resultHandler(event) {

   console.log("Should modify content after it is freshly loaded/rendered, but doesn't work");

    event.target.querySelectorAll("li").forEach((li) => { 
      li.setAttribute("loaded", true); // <li loaded="true"></li>
    });
  }
}

The .erb file returned by the search request is like this (note: actually, if this can be the problem but I think it’s not, the response to the search fetch request is a <turbo-stream><template><ul><li>...</template></turbo-stream> which properly loads/renders the content):

<turbo-stream action="update" target="search_turbo_frame_id"><template>
    <ul>
      <li>Item 1</li>
      <li>Item 2</li>
      <li>Item 3</li>
      <li>Item ...</li>
    </ul>
</template></turbo-stream>

This may help.