Interacting with tables 'get_row_index_of_first_row_with_value_in_column'

I have a view containing a table populated with a binding to a named database query.
I'm wanting to check if the table contains some expected data so use 'get_row_index_of_first_row_with_value_in_column' to search for it.
I am finding that the method above is always returning 0

Edit: I am using ignition perspective 8.1.33

I've never heard of that. Where did you read about it or is it something you wrote?

Its in the Ignition automation tools IgnitionAutomationTools\Components\PerspectiveComponents\Common\Table.py

I think you are referring to the tools below.

You're not making it easy for anyone to help you. Provide proper hyperlinks. Provide code and format it properly as shown in Wiki - how to post code on this forum. Provide test data so the problem can be reproduced.

Yep thats exactly what I'm using. Sorry I assumed the context around that was provided by the automated testing tag of my original post

Bit hard to provide test data as this is a small part of a large project.

I'll give it a go on Monday though.

Feel free to grab the dataset.vlookup() function from here:

1 Like

Could you provide an example of how you're using it? Could you provide a pair of column_id and known_value values for which this is occurring for you?

We use that function quite a few times to obtain alarm event ids within our Alarm Status Table testing, so the function does work. There are three scenarios which could potentially result in you always seeing a value of 0:

  1. Your table has the value you are searching for in the 0th row.
  2. The locator used to identify your table is incorrect, and you are searching another table - which just happens to also have your value in a column of the same name.
  3. The locator you are using matches two tables on the same page - and the first table happens to have the value you are searching for in a column of the same name.

Can you provide a screenshot of the view/page in question during the exact time and in the same state as when you are encountering the undesirable behavior?

1 Like

There is a bit going on so i will try to explain it as concisely as possible

I have page that contains
A tab selector
with a nested tab selector
There are then dropdowns that are driving inputs to a database query that the table data is bound to

I double checked that i am interacting with the correct table. That's why i call 'set_cell_data_by_row_index_column_id' which seems to work fine (but did require that I double click the correct cell first).

here is an excerpt of the exact code as run.

row_num = self.page_inst._View_ElementConfig._View_ElementAlarms.get_row_index_of_first_row_with_value_in_column('name', alarm_name)

message_index = self.page_inst._View_ElementConfig._View_ElementAlarms.get_column_index('message')
self.page_inst._View_ElementConfig._View_ElementAlarms.double_click_cell_in_body(row_num,message_index)
self.page_inst._View_ElementConfig._View_ElementAlarms.set_cell_data_by_row_index_column_id(row_num,'message',alarm_name)

data = self.page_inst._View_ElementConfig._View_ElementAlarms.get_cell_data_by_column_id(row_num,'name')
assert data==alarm_name, f'expected: {alarm_name}, instead got {data}'

The screenshot provided above was taken during the test so you can see that i am searching for the string "Fail To De-Energise" but still got returned row 0.
The test fails with

FAILED tests/Test_6_16_ElementAlarms.py::ElementAlarms::test_element_alarms[alarm_instance1] - AssertionError: expected: Fail To De-Energise, instead got Element Input References

The column props from the table are below, you can see that the column name is 'name' just with the header 'Alarm'

If i search for a string that is not contained in the table i get

selenium.common.exceptions.TimeoutException: Message: Unable to locate element with CSS locator: ('css selector', '[id="Table_ElementAlarms"] div.tb div.tr-group[data-row-index="None"] div.ia_table__body__row div.tc[data-column-id="name"]')

Okay, I found the issue.

Perspective Tables use Row Groups. These row groups could have multiple rows. This might sound weird, but this is how expandable row sub-views are represented; a rendered sub-view is actually a whole different row within the row group. This impacts us here because the standard table will always return a data-row-index value of 0.

So, Perspective Tables should actually use the data-row-id as a unique identifier for the row in question, and then use that ID to determine the row index of the row group that contained the relevant cell.

The automation code fix is going to be to overwrite the behavior within Perspective Tables. I will be inserting the following code within Components/PerspectiveComponents/Displays/Table.py:

    def get_row_index_of_first_row_with_value_in_column(
            self, column_id_with_known_value: str, known_value: str) -> Optional[int]:
        """
        Obtain the index of the first row which contains a specified value in a known column.

        :param column_id_with_known_value: The case-sensitive column ID of the column that you want to search.
            Note that this may not always be the name of the column, as this attribute is mapped to the data key which
            drives the column.
        :param known_value: The case-sensitive value you are searching for within the column.

        :returns: The zero-based index of the first row which contains the specified known_value in the specified
            column, or None if no row contained the value.
        """
        _locator = (By.CSS_SELECTOR, f'{self.BODY_CELL_CSS}[data-column-id="{column_id_with_known_value}"]')
        _cells = ComponentPiece(
            locator=_locator,
            driver=self.driver,
            parent_locator_list=self.locator_list,
            poll_freq=self.poll_freq).find_all()
        # Perspective Table component uses Row Groups, and so we need to use the row id to obtain the row index.
        row_id = None
        for cell in _cells:
            if cell.text == str(known_value):
                row_id = int(cell.get_attribute("data-row-id"))
        if row_id is None:
            return None
        else:
            _locator = (
                By.CSS_SELECTOR,
                f'{self.ROW_GROUP_CSS}[data-row-id="{row_id}"]')
            row_group = ComponentPiece(
                locator=_locator,
                driver=self.driver,
                parent_locator_list=self.locator_list,
                poll_freq=self.poll_freq)
            return int(row_group.find().get_attribute('data-row-index'))

The reason we had not encountered this internally is that we don't use the function in question within standard tables - only within Alarm tables, where these row group special wrappers do not exist.

4 Likes

Yep that worked. Thanks heaps mate.

1 Like

You're very welcome. While the fix worked, I wasn't a fan of how I implemented it. After thinking about this a bit overnight, I like a different approach. You should be able to view the diffs when we actually merge the changes, but what I'm going to do is provide the common table with a function like this:

    @classmethod
    def _get_row_index(cls, web_element: WebElement) -> int:
        return int(web_element.get_attribute("data-row-index"))

Then, the Perspective Table component will just have its own override of that function which leans on the data-row-id attribute:

    @classmethod
    def _get_row_index(cls, web_element: WebElement) -> int:
        return int(web_element.get_attribute("data-row-id"))

This allows the two classes to have control over how their respective row indices are determined, and avoids the need to re-query based on the row id.

1 Like