Skip to content

render generates flaky element selectors, causing tests to fail #42

@nizans

Description

@nizans

Hi

The render function appears to generate element locators based on baseElement (by default document.body) instead of the container.

I’m not sure if this is intentional, but my expectation was that the container would be used as the base for the element selectors.

This seems to come from this line in pure.tsx:

...getElementLocatorSelectors(baseElement),

The main issue I’m seeing is that when page.elementLocator is called with document.body, it can create a selector that isn’t stable.
Instead of a simple body selector, the generated selector uses the body’s text content.

If the body content changes over time, the base selector can become invalid, which then causes tests to fail even though the DOM is in the expected state.

Locally, I was able to resolve this by changing the line in pure.tsx to use container instead of baseElement.
But Im not sure if this is the right solution and if it was intended or not.

Adding a simple code reproduction here:

import React from "react";
import { expect, test } from "vitest";
import { render } from "vitest-browser-react";
import { page } from "vitest/browser";

const LoadingComponent = ({ timeout = 1500 }: { timeout?: number }) => {
  const [show, setShow] = React.useState(false);

  React.useEffect(() => {
    const timer = setTimeout(() => setShow(true), timeout);
    return () => clearTimeout(timer);
  }, [timeout]);

  return <div>{show ? "Hello Vitest!" : "Loading..."}</div>;
};

test("renders name", async () => {
  // Put some stuff in the body
  const stuff = document.createElement("div");
  stuff.textContent = "Stuff That Changes";
  document.body.appendChild(stuff);

  // Stuff changed
  setTimeout(() => {
    stuff.textContent = "Im no longer there";
  }, 1000);

  const res = await render(<LoadingComponent />);

  console.log("The expected container locator", res.locator); // "div >> nth=1"

  const baseElementSelector = page.elementLocator(res.baseElement).selector;
  console.log(baseElementSelector); // Expected "body" but got "internal:text="Stuff That ChangesLoading...!"i"

  const theProblematicSelector = page.getByText("Hello Vitest!").selector;
  console.log(theProblematicSelector); // internal:text="Stuff That ChangesHello Vitest!"i >> internal:text="Hello Vitest!"i
  // ^^ This does not use the "container" as the base locator
  // ^^ The selector generated by page.elementLocator(baseElement) where baseElement is document.body results in a flaky locator

  await expect
    .element(res.getByText("Hello Vitest!"), {
      timeout: 3000,
      interval: 200,
    })
    .toBeVisible(); // Fails even though "Hello Vitest!" is there
   // Cannot find element with locator: getByText('Stuff That   ChangesLoading...').getByText('Hello Vitest!')
});

Thanks

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions