Machtyn
Machtyn

Reputation: 3272

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"

Upvotes: 2

Views: 7461

Answers (3)

M Andr&#225;s
M Andr&#225;s

Reputation: 169

The easiest way is to use the row as aria role

page.getByRole('row')

and filter it by the text of the cell

.filter({ hasText: 'Concert' })

together:

page.getByRole('row').filter({ hasText: 'Concert' })

fnially, locate the checkbox and chain it

page.getByRole('row').filter({ hasText: 'Concert' }).getByRole('checkbox')

See docs https://playwright.dev/docs/next/api/class-framelocator#frame-locator-get-by-role

Upvotes: 0

Machtyn
Machtyn

Reputation: 3272

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();
}

Upvotes: 1

creage
creage

Reputation: 190

Well, since you don't have any explicit selectors you can bind to, the only option is to reduce:

  1. You collect all of trs.
  2. You pick trs that have tds of kind Concert
  3. From these, you pick trs that have tds appearing on 06/10/2022
  4. You click on collected element(s)

Upvotes: 2

Related Questions