puppeteer icon indicating copy to clipboard operation
puppeteer copied to clipboard

Allow additional aria attributes to be specified in aria/ queries

Open andyearnshaw opened this issue 2 years ago • 6 comments

I know there's probably an upstream issue involved here since this feature is reliant on the CDP endpoint being improved, but since that CDP endpoint was originally implemented for Puppeteer I thought it was worth adding here.

Currently, only [role="rolename"] and label are permitted in aria/ queries. This is a bit of a hindrance in certain circumstances. For instance, let's say I want to select the row label for the 4th row in a grid. To do this at the moment, I need to use a query for all the rows in the grid and then use array indexing to pick the 4th item.

For example:

const grid = await page.$('aria/Users[role="grid"]');
const rowHeadings = await grid.$$('aria/[role="rowheading"]');
expect(await rowHeadings[3].getProperty("textContent")).toEqual("Andy");

However, this code will fail if the grid is only a partial rendering (e.g. a grid that uses virtual scrolling to improve page performance). Instead, I need to be able to query by row index:

const grid = await page.$('aria/Users[role="grid"]');
const rowHeading4 = await grid.$('aria/[role="rowheading"][rowindex="4"]');
expect(await rowHeading4.getProperty("textContent")).toEqual("Andy");

I could fallback to CSS selectors and query by aria-rowindex but this requires extra hoops if aria-rowindex is set on the role="row" nodes and not on the role="rowheading" nodes.

Another, perhaps more egregious example might be a radiogroup. I want to get the currently selected option, but instead of

const group = await scope.$('aria/Text alignment[role="radiogroup"]');
const selected = await group.$('aria/[checked="true"]');

I have to do

const group = await scope.$('aria/Text alignment[role="radiogroup"]');
const radios = await scope.$('aria/[role="radio"]');
let selected;
for (const radio of radios) {
    if (await radio.evaluate(r => r.checked || (r.ariaChecked === "true"))) {
        selected = radio;
        break;
    }
}

andyearnshaw avatar Oct 25 '21 15:10 andyearnshaw

I know there's probably an upstream issue involved here since this feature is reliant on the CDP endpoint being improved, but since that CDP endpoint was originally implemented for Puppeteer I thought it was worth adding here.

On second thoughts, we could probably filter them on the Puppeteer side for now, the same way that StaticText nodes are filtered out. Would a PR for this be acceptable (if I can find the time), or would the preference be to have this implemented in Chromium?

andyearnshaw avatar Nov 03 '21 09:11 andyearnshaw

I'm not against filtering at the Puppeteer side but unfortunately not all properties are exposed through CDP AXNodes. For example, I don't think ColumnIndex and RowIndex are available. If we need to add these at the Chromium level anyway, it might be easier to just go ahead and implement the filtering there too. Which properties would be most interesting for querying? Something like disabled, focusable, hidden, level, checked, expanded, selected, rowIndex, columnIndex?

johanbay avatar Nov 09 '21 08:11 johanbay

Which properties would be most interesting for querying? Something like disabled, focusable, hidden, level, checked, expanded, selected, rowIndex, columnIndex?

Those would cover the majority of use cases I think. Perhaps haspopup too (query a button or input that has a popup menu).

andyearnshaw avatar Nov 09 '21 17:11 andyearnshaw

This is how I'm having to implement getting a specific cell:

async function getGridCell(
  page,
  targetColumnIndex,
  targetRowIndex,
  parentSelector
) {
  const parentHandle = page.$(parentSelector);
  const gridCells = await parentHandle.$$(`aria/[role="gridcell"]`);

  for (const cellHandle of gridCells) {
    const { rowIndex, columnIndex } = await cellHandle.evaluate(node => ({
      rowIndex: node.ariaRowIndex,
      columnIndex: node.ariaColIndex
    }));

    if (+rowIndex === targetRowIndex && +columnIndex === targetColumnIndex) {
      return cellHandle;
    }
  }

  throw new Error(
    `Failed to find grid cell ${targetColumnIndex}, ${targetRowIndex}${
      parentSelector ? ` under ${parentSelector}` : ""
    }`
  );
}

This creates a lot of back and forth between puppeteer and the browser. My hope is that this would eventually be as simple as

const gridCell = await parentHandle.$$(
  `aria/[role="gridcell"][rowindex="${targetRowIndex}][colindex="${targetColumnIndex}"]`
);

Does this need an upstream ticket on https://crbug.com? Bearing in mind I've never contributed to Chromium before, is there anything I can do to help?

andyearnshaw avatar May 19 '22 13:05 andyearnshaw

We're marking this issue as unconfirmed because it has not had recent activity and we weren't able to confirm it yet. It will be closed if no further activity occurs within the next 30 days.

stale[bot] avatar Aug 06 '22 13:08 stale[bot]

@jrandolf I think you might have tagged this one incorrectly.

andyearnshaw avatar Aug 08 '22 13:08 andyearnshaw