reactjsnext.jsplaywrightplaywright-typescript

Unable to select option within select element with playwright


I have the component below that loads a list of countries from an API. It uses the list to populate the options under a select HTML element.

import { CountryDto } from '@/lib/models';
import { CountryService } from '@/services';
import { Select } from 'flowbite-react';
import React, { ChangeEvent, ChangeEventHandler, useEffect, useMemo, useState } from 'react';

type CountrySelectorProps = {
  value?: string;
  onChange?: ChangeEventHandler<HTMLSelectElement>;
  id?: string;
  name?: string;
  isRequired?: boolean;
  isDisabled?: boolean;
  textAsValue?: boolean;
  selectedCountry?: CountryDto;
  "data-testid"?: string
};

const CountrySelector = ({
  value,
  onChange,
  id,
  name,
  isRequired,
  isDisabled,
  textAsValue,
  selectedCountry,
  "data-testid": dataTestId
}: CountrySelectorProps) => {
  const [countries, setCountries] = useState<CountryDto[]>([]);
  const [selectedValue, setSelectedValue] = useState<string>(value || '');
  const [isLoading, setIsLoading] = useState(true);

  useEffect(() => {
    setIsLoading(true);
    CountryService.getCountries().then(result => {
      if (result.success) {
        setCountries(result.data || []);
      }
      setIsLoading(false);
    }).catch(() => {
      setIsLoading(false);
    });
  }, []);

  useEffect(() => {
    setSelectedValue(value || (selectedCountry ? (textAsValue ? selectedCountry.name : selectedCountry.iso2Code) : '') || '');
  }, [value, selectedCountry, textAsValue]);

  const handleChange = (event: ChangeEvent<HTMLSelectElement>) => {
    const selectedCountryValue = event.target.value;
    setSelectedValue(selectedCountryValue);
    onChange && onChange(event);
  };

  return (
    <Select
      id={id}
      name={name}
      value={selectedValue}
      onChange={handleChange}
      required={isRequired}
      disabled={isDisabled || isLoading}
      data-testid={dataTestId}
    >
      {isLoading ? (
        <option value="">Loading...</option>
      ) : (
        <>
          <option value="">{selectedCountry ? selectedCountry.name : 'Select a country'}</option>
          {countries.map(country => (
            <option
              key={country.iso2Code}
              value={textAsValue ? country.name as string : country.iso2Code as string}
            >
              {country.name}
            </option>
          ))}
        </>
      )}
    </Select>
  );
};

export default CountrySelector;

The component works as expected on the UI. One of the rendered options is

<option value="France">France</option>

The parent component uses the CountrySelector as below

<CountrySelector
    value={companyBillingDetails.companyBillingCountry}
    data-testid="companyBillingCountry"
    onChange={event =>
        setCompanyBillingDetails({
                ...companyBillingDetails,
                companyBillingCountry: event.target.value,
              })
    }
    isRequired
    textAsValue
    isDisabled={isSubmitting}
/>

My test is as follows:

test('should handle new client billing details', async ({ page }) => {
    // Fill out billing details
   
    // Wait for country selector to load and be interactable
    await page.waitForSelector('[data-testid="companyBillingCountry"]:not([disabled])', { timeout: 15000 });

    // Select country
    await page.selectOption('[data-testid="companyBillingCountry"]', 'France');

    
    // Submit the form
    const submitButton = await page.getByTestId('submit-job-button');
    await submitButton.click();

    // Check for success toast
    const successToast = await page.getByText('Job submitted successfully');
    expect(successToast).toBeDefined();
  });

But I got the below error after running it

Error: page.selectOption: Test timeout of 30000ms exceeded.
    Call log:
      - waiting for locator('[data-testid="companyBillingCountry"]')
        - locator resolved to <select required="" data-testid="companyBillingCountry" class="block w-full border disabled:cursor-not-allowed disabled:opacity-50 border-gray-300 bg-gray-50 text-gray-900 focus:border-cyan-500 focus:ring-cyan-500 dark:border-gray-600 dark:bg-gray-700 dark:text-white dark:placeholder-gray-400 dark:focus:border-cyan-500 dark:focus:ring-cyan-500 p-2.5 text-sm rounded-lg">…</select>
      - attempting select option action
        2 × waiting for element to be visible and enabled
          - did not find some options
        - retrying select option action
        - waiting 20ms
        2 × waiting for element to be visible and enabled
          - did not find some options
        - retrying select option action
          - waiting 100ms
        55 × waiting for element to be visible and enabled
           - did not find some options
         - retrying select option action
           - waiting 500ms


      133 |
      134 |     // Select country
    > 135 |     await page.selectOption('[data-testid="companyBillingCountry"]', 'France');
          |                ^
      136 |

I can't seem to find why the test wouldn't select the France option


Solution

  • You don't need to use waitForSelector because of auto-waiting https://playwright.dev/docs/actionability

    Try to find with label, text and with { force: true } to bypass auto-wait.

    // for value
    await page.getByTestId("companyBillingCountry").selectOption("France");
    
    // for label
    await page.getByTestId("companyBillingCountry").selectOption({ label: "France" });
    

    And maybe printing innerHTML() of that locator would help.