#Play Function - find elements

3 messages · Page 1 of 1 (latest)

tall prairie
#

In general, I have problems targeting elements if they do not have a label/role. For example,
If I have a textarea like this

<>
      {label && (
        <label htmlFor={id} className={styles.label}>
          {label}
        </label>
      )}
      <textarea
        id={id}
        name={name}
        placeholder={placeholder}
        className={styles.textarea}
        {...props}
      >
        {content}
      </textarea>
    </>

How do I target it with the play-function?
My current solution is:

const meta: Meta<typeof TextInput> = {
  component: TextInput,
  title: 'TextInput',
  tags: ['autodocs'],
  args: {
    id: 'foo',
    label: 'Label',
    placeholder: 'Placeholder',
  },
} satisfies Meta<typeof TextInput>;

export default meta;

type Story = StoryObj<typeof TextInput>;

export const WriteText: Story = {
  args: {
    content: 'hallo',
  },
  name: 'User Input: Click Textarea and Write',
  play: async ({ canvasElement }) => {
    const canvas = within(canvasElement);
    const textarea = canvas.getByText('hallo');
    await userEvent.click(textarea);
    await userEvent.keyboard('Hello');
  },
};

but doesn't feel right. Isn't there something like passing a test-id in the props so I can target the element? I don't want the data-test-id in my native component though. So yeah, generally, how do you target simple elements like textareas, inputs etc.?

frigid mortar
#

Hi! There are a few aspects to your question. Hopefully this will be clear! 😅

First, just so you can be more equipped the next time you have questions like this, all of the queries (getByText, findByLabelText, etc) come directly from https://testing-library.com/docs/queries/about. So all of the documentation and guidance there applies.

Secondly, const textarea = canvas.getByLabelText('Label'); should work.

Third, useEvent.type is more appropriate here than userEvent.click + userEvent.keyboard. From the docs:

You should use userEvent.keyboard if you want to just simulate pressing buttons on the keyboard. You should use userEvent.type if you just want to conveniently insert some text into an input field or textarea.
https://testing-library.com/docs/ecosystem-user-event#keyboardtext-options

Fourth, if you want to avoid hard-coding that label text, you can access the arg values in your play function:

const meta = {
  component: TextInput,
  title: 'TextInput',
  tags: ['autodocs'],
  args: {
    id: 'foo',
    label: 'Label',
    placeholder: 'Placeholder',
  },
} satisfies Meta<typeof TextInput>;

export default meta;

type Story = StoryObj<typeof meta>;

export const WriteText = {
  name: 'User Input: Click Textarea and Write',
  play: async ({ canvasElement, args }) => {
    const canvas = within(canvasElement);
    const textarea = canvas.getByLabelText(args.label);
    await userEvent.type(textarea, 'Hello');
  },
} satisfies Story;
#

And finally, yes, getByTestId will query by the data-testid attribute. Testing-library recommends this as a last resort, when no other queries fit your use case. This is because other queries, like getByLabelText match how your users will actually use your component, while getByTestId is an internal detail. We agree with that guidance.

Your component spreads remaining props onto the textarea, so you're able to define args: { 'data-testid': 'foo' }. If that data attribute isn't a valid prop for TextInput, you'd then need to adjust your CSF types:

const meta = {
  // ...
} satisfies Meta<React.ComponentProps<TextInput> & { 'data-testid'?: string }>;

// ... rest of file unchanged

Btw, if you're using satisfies, you don't also need to assign meta: Meta.

This may be helpful reading on typing stories: