playwright

Selecting a specific row from a table using two text criteria (Playwright, js)


I'm having trouble using potential unique identifiers to select a specific row from a table.

Given the following table

#events {
  font-family: Arial, Helvetica, sans-serif;
  border-collapse: collapse;
  width: 100%;
  margin: 10px 0 20px 0;
}

#events td,
#events th {
  border: 1px solid #ddd;
  padding: 8px;
}

#events tr:nth-child(even) {
  background-color: #f2f2f2;
}

#events tr:hover {
  background-color: #ddd;
}

#events th {
  padding-top: 12px;
  padding-bottom: 12px;
  text-align: left;
  background-color: #04AA6D;
  color: white;
}
<div>
  <table id="events" class="table table-striped">
    <thead>
      <tr>
        <th data-qaid="checkbox"><input type="checkbox" /></th>
        <th data-qaid="event">Event</th>
        <th data-qaid="date">Date</th>
        <th data-qaid="location">Location</th>
        <th data-qaid="purchase-date">Ticket Purchased</th>
      </tr>
    </thead>
    <tbody>
      <tr>
        <td data-qaid="checkbox"><input type="checkbox" /></td>
        <td data-qaid="event">Movie</td>
        <td data-qaid="date">06/06/2022</td>
        <td data-qaid="location">Frankfort</td>
        <td data-qaid="purchase-date">06/06/2022</td>
      </tr>
      <tr>
        <td data-qaid="checkbox"><input type="checkbox" /></td>
        <td data-qaid="event">Concert</td>
        <td data-qaid="date">06/06/2022</td>
        <td data-qaid="location">Frankfort</td>
        <td data-qaid="purchase-date">06/06/2022</td>
      </tr>
      <tr>
        <td data-qaid="checkbox"><input type="checkbox" /></td>
        <td data-qaid="event">Park</td>
        <td data-qaid="date">06/10/2022</td>
        <td data-qaid="location">Memphis</td>
        <td data-qaid="purchase-date">06/06/2022</td>
      </tr>
      <tr>
        <td data-qaid="checkbox"><input type="checkbox" /></td>
        <td data-qaid="event">Concert</td>
        <td data-qaid="date">06/10/2022</td>
        <td data-qaid="location">Memphis</td>
        <td data-qaid="purchase-date">06/06/2022</td>
      </tr>
      <tr>
        <td data-qaid="checkbox"><input type="checkbox" /></td>
        <td data-qaid="event">Sport</td>
        <td data-qaid="date">06/12/2022</td>
        <td data-qaid="location">Atlanta</td>
        <td data-qaid="purchase-date">06/06/2022</td>
      </tr>
    </tbody>
  </table>
</div>
<div id="change-location">
  <label>New Location</label>
  <input type="text" />
  <button>Update Selected</button>
</div>

Using a playwright Locator, I want to be able to select the row for the Concert on 06/10/2022 and then be able to click the checkbox on that row.

I've been able to do this using a single column locator and selecting the first row encountered (using .nth(#)).

const child_locator = page.locator('[data-qaid="Event"]', { hasText: "Concert" }).nth(0);
const row_locator = page.locator("#events tbody tr", { has: child_locator });
row_locator.locator('[data-qaid="checkbox"] input').setChecked();

But this won't work, since my desired row would not be the first encountered row result.

I'd like to have something more robust / dynamic. And I'm having a hard time figuring out how to do this. I've tried various things like combining locators, using the resultant array of Locators to restrict subsequent searches. I think it comes from my understanding of Playwright locators being as complete as it can be. And I am studying the docs, but haven't figured this out yet.

I think my only real solution may be to get the text of the entire row and just regex on that. But this may have issues with false positives if the text being searched for appears in another column on a row. Such as in the given example, if I wanted to choose the Concert with Date of "06/06/2022", my regex would still select the Concert with Date "06/10/2022" since the Ticket Purchased would also match "06/06/2022"


Solution

  • Here's what I came up with:

    /**
     * options {{page: Page, search_array: [{column_index: number, needle: string}], tr_selector: Locator}}
     */
    static async getRowByValues(options) {
       // get the table data
       const table_data = await options.page.locator(table_selector).allInnerTexts();
       let row_index = table_data.findIndex( (row_text) => {
          const text_array = row_text.split("\t");
          // loop through the search_array data and match to text_array
          for (const col_data of options.search_array) {
             // fail immediately if false, continue if true.
             if (text_array[col_data.column_index] !== col_data.needle) {
                return false;
             }
          }
          return true;
       });
       if (row_index >= 0) {
          return options.page.locator(tr_selector).nth(row_index);
       } else {
          return null;
       }
    }
    

    And to use this, one could do:

    const desired_row = await getRowByValues({
       page: page,
       search_array: [
         {
           column_index: 1,
           needle: "Concert"
         },
         {
           column_index: 2,
           needle: "06/10/2022"
         }
       ],
       tr_selector: page.locator('#events tbody tr')
    });
    
    if (desired_row) {
       await desired_row.locator('[data-qaid="checkbox"] input').setChecked(true);
       await expect(desired_row.locator('[data-qaid="checkbox"] input')).toBeChecked();
    }