Skip to content

TableComponent

Модуль компонента таблицы. Содержит класс для работы с табличными данными.

TableComponent

Bases: BaseComponent

Компонент таблицы. Предоставляет методы для работы с табличными данными.

Source code in components\table_component.py
 12
 13
 14
 15
 16
 17
 18
 19
 20
 21
 22
 23
 24
 25
 26
 27
 28
 29
 30
 31
 32
 33
 34
 35
 36
 37
 38
 39
 40
 41
 42
 43
 44
 45
 46
 47
 48
 49
 50
 51
 52
 53
 54
 55
 56
 57
 58
 59
 60
 61
 62
 63
 64
 65
 66
 67
 68
 69
 70
 71
 72
 73
 74
 75
 76
 77
 78
 79
 80
 81
 82
 83
 84
 85
 86
 87
 88
 89
 90
 91
 92
 93
 94
 95
 96
 97
 98
 99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
class TableComponent(BaseComponent):
    """Компонент таблицы. Предоставляет методы для работы с табличными данными."""

    def __init__(self, page: Page):
        """Инициализирует компонент таблицы.

        Args:
            page: Экземпляр страницы Playwright.
        """

        super().__init__(page)

    # Действия:
    def click_arrow_button(self, table_locator: str | Locator, index: int) -> None:
        """ Нажатие кнопки-стрелочки вверх/вниз в ячейке заголовка таблицы

        Args:
            table_locator: Локатор таблицы.
            index: Индекс ячейки в заголовке.
        """

        arrow_button = self.get_header_cell_button(table_locator, index)
        assert arrow_button.is_enabled(), f"Arrow button is missing in {index} header cell"
        arrow_button.click()

    def datetime2timestamp(self, date_string: str) -> float|None:
        """ Конвертация строкового представления даты и времени в Unix timestamp
        Args:
            date_string: Строка с датой и временем в формате d.m.Y H:M:S.

        Returns:
            float: Unix timestamp.
            None: конвертация невозможна
        """

        # Формат, соответствующий строке с датой и временем
        format_string = "%d.%m.%Y %H:%M:%S"

        try:
            date_object = datetime.strptime(date_string, format_string)
            return date_object.timestamp()
        except ValueError:
            return None

    def get_arrow_button_state(self, table_locator: str | Locator, index: int) -> str:
        """ Получение состояния кнопки-стрелочки вверх/вниз в ячейке заголовка таблицы

        Args:
            table_locator: Локатор таблицы.
            index: Индекс ячейки в заголовке.

        Returns:
            up, если это стрелочка вверх. down, если это стрелочка вниз.
        """

        arrow_button = self.get_header_cell_button(table_locator, index)
        assert arrow_button.is_enabled(), f"Arrow button is missing in {index} header cell"

        state = arrow_button.inner_text()
        if state == "keyboard_arrow_up":
            return "up"
        elif state == "keyboard_arrow_down":
            return "down"
        else:
            assert False, f"Got unsupported arrow state: {state}"

    def get_header_cell_button(self, table_locator: str | Locator, index: int) -> Locator:
        """ Поиск кнопки в ячейке заголовка таблицы

        Args:
            table_locator: Локатор таблицы.
            index: Индекс ячейки в заголовке.

        Returns:
            Локатор строки кнопки.

        Raises:
            AssertionError: Если индекс вне диапазона.
        """

        table = self.get_locator(table_locator)
        header_cells_count = table.locator("//thead/tr/th").count()
        assert index in range(header_cells_count), "Header cell index is out of range"
        return table.locator("//thead/tr/th").nth(index).get_by_role("button")

    def get_row_locator(self, table_locator: str | Locator, row_index: int) -> Locator | None:
        """Возвращает локатор строки по индексу.

        Args:
            table_locator: Локатор таблицы.
            row_index: Индекс строки.

        Returns:
            Локатор строки или None, если индекс вне диапазона.
        """

        table = self.get_locator(table_locator)

        rows = table.locator("//tbody/tr")

        if row_index in range(rows.count()):
            return rows.nth(row_index)
        else:
            return None

    def get_rows_count(self, locator: str | Locator) -> int:
        """Возвращает количество строк в таблице (без заголовка).

        Returns:
            int: Количество строк с данными.

        Raises:
            AssertionError: Если таблица пуста.
        """

        table_content = self.read(locator)
        rows_count = len(table_content)

        if rows_count == 0:
            assert False, "The contents of the table are missing"

        return rows_count - 1


    def read(self, locator: str | Locator) -> list[list[str]]:
        """Читает данные таблицы, включая заголовки.

        Args:
            locator: Локатор таблицы.

        Returns:
            Двумерный список с данными таблицы.
        """

        table_data = []
        table = self.get_locator(locator)

        # Чтение заголовка таблицы
        header_cells = table.locator("//thead/tr")
        header_cell_text = header_cells.nth(0).inner_text()
        header_data = header_cell_text.split('\n')
        table_data.append(header_data)

        # Чтение ячеек таблицы
        rows = table.locator("//tbody/tr")
        for i in range(rows.count()):
            row = rows.nth(i)
            cells = row.locator("td")
            row_data = []
            for j in range(cells.count()):
                cell_text = cells.nth(j).inner_text()
                row_data.append(cell_text)
            table_data.append(row_data)

        return table_data

    # Проверки:
    def check_table_headers(self, actual_headers, expected_headers) -> None:
        """ Проверка соответствия заголовка таблицы ожидаемому"""

        is_equals = True

        arrow_state = ["keyboard_arrow_down", "keyboard_arrow_up"]

        for item in actual_headers:
            item = item.strip()

            if item in arrow_state:
                continue

            if item not in expected_headers:
                is_equals = False

        assert is_equals, \
            f"Expected events table headers {expected_headers} are not equal {actual_headers}"

    def check_content(self,
                    locator: str | Locator,
                    expected_headers: list[str],
                    check_table_not_empty: bool = True) -> None:
        """Проверяет содержимое таблицы.

        Проверяет заголовки и наличие данных в таблице.

        Args:
            locator: Локатор таблицы.
            expected_headers: Список ожидаемых заголовков таблицы.
            check_table_not_empty: Флаг проверки, что таблица не пустая.
                                По умолчанию True.

        Raises:
            AssertionError: Если таблица пуста (при check_table_not_empty=True)
                        или заголовки неверны.
        """

        table_content = self.read(locator)

        if len(table_content) == 0:
            assert False, "The contents of the table are missing"

        # Проверка заголовков таблицы
        self.check_table_headers(table_content[0], expected_headers)

        # Проверка наличия данных в таблице
        if len(table_content) == 1:
            if check_table_not_empty:
                assert False, "Table body is missing"
            else:
                logger.info("Таблица пустая (не содержит строк с данными)")

    def check_column_descending_order(self,
                                         locator: str | Locator,
                                         index: int,
                                         convert2timestamp=False) -> bool:
        """Проверка, что заданный столбец таблицы упорядочен по убыванию.

        Args:
            locator: Локатор таблицы.
            index: Индекс столбца.
            convert2timestamp: Конвертировать строковое представление даты и времени в Unix timestamp

        Returns:
            True, если столбец таблицы упорядочен по убыванию. Иначе: False
        """

        table_content = self.read(locator)

        if len(table_content) == 0:
            assert False, "The contents of the table are missing"

        del table_content[0]

        assert index in range(len(table_content[0])), \
                            "Column index is out of range"
        column = []
        for i in range(len(table_content)):
            if convert2timestamp:
                timestamp = self.datetime2timestamp(table_content[i][index])
                assert timestamp, f"Error conversation to timestamp for {table_content[i][index]}"
                column.append(timestamp)
            else:
                column.append(table_content[i][index])

        return all(column[i] >= column[i+1] for i in range(len(column) - 1))

    def check_first_row_visibility(self, locator: str | Locator) -> None:
        """Проверяет видимость первой строки таблицы.

        Args:
            locator: Локатор таблицы.
        """

        table = self.get_locator(locator)
        first_row = table.locator("//tbody/tr").first
        expect(first_row).to_be_visible(), "The first table row is not visible"

    def check_last_row_visibility(self, locator: str | Locator) -> None:
        """Проверяет видимость последней строки таблицы.

        Args:
            locator: Локатор таблицы.
        """

        table = self.get_locator(locator)
        last_row = table.locator("//tbody/tr").last
        expect(last_row).to_be_visible(), "The last table row is not visible"

    def check_row_highlighting(self, locator: str | Locator, row_index: int) -> None:
        """Проверяет изменение цвета строки при наведении.

        Args:
            locator: Локатор таблицы.
            row_index: Индекс проверяемой строки.
        """

        table = self.get_locator(locator)
        row = table.locator("//tbody/tr").nth(row_index)

        row.scroll_into_view_if_needed()
        hover_element = row.locator(".body-row-hover")
        initial_color = hover_element.evaluate("el => window.getComputedStyle(el).backgroundColor")

        row.hover()
        self.page.wait_for_timeout(300)

        new_color = hover_element.evaluate("el => window.getComputedStyle(el).backgroundColor")
        assert initial_color != new_color, "Color of row did not change when hovering the cursor"

__init__(page)

Инициализирует компонент таблицы.

Parameters:

Name Type Description Default
page Page

Экземпляр страницы Playwright.

required
Source code in components\table_component.py
15
16
17
18
19
20
21
22
def __init__(self, page: Page):
    """Инициализирует компонент таблицы.

    Args:
        page: Экземпляр страницы Playwright.
    """

    super().__init__(page)

check_column_descending_order(locator, index, convert2timestamp=False)

Проверка, что заданный столбец таблицы упорядочен по убыванию.

Parameters:

Name Type Description Default
locator str | Locator

Локатор таблицы.

required
index int

Индекс столбца.

required
convert2timestamp

Конвертировать строковое представление даты и времени в Unix timestamp

False

Returns:

Type Description
bool

True, если столбец таблицы упорядочен по убыванию. Иначе: False

Source code in components\table_component.py
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
def check_column_descending_order(self,
                                     locator: str | Locator,
                                     index: int,
                                     convert2timestamp=False) -> bool:
    """Проверка, что заданный столбец таблицы упорядочен по убыванию.

    Args:
        locator: Локатор таблицы.
        index: Индекс столбца.
        convert2timestamp: Конвертировать строковое представление даты и времени в Unix timestamp

    Returns:
        True, если столбец таблицы упорядочен по убыванию. Иначе: False
    """

    table_content = self.read(locator)

    if len(table_content) == 0:
        assert False, "The contents of the table are missing"

    del table_content[0]

    assert index in range(len(table_content[0])), \
                        "Column index is out of range"
    column = []
    for i in range(len(table_content)):
        if convert2timestamp:
            timestamp = self.datetime2timestamp(table_content[i][index])
            assert timestamp, f"Error conversation to timestamp for {table_content[i][index]}"
            column.append(timestamp)
        else:
            column.append(table_content[i][index])

    return all(column[i] >= column[i+1] for i in range(len(column) - 1))

check_content(locator, expected_headers, check_table_not_empty=True)

Проверяет содержимое таблицы.

Проверяет заголовки и наличие данных в таблице.

Parameters:

Name Type Description Default
locator str | Locator

Локатор таблицы.

required
expected_headers list[str]

Список ожидаемых заголовков таблицы.

required
check_table_not_empty bool

Флаг проверки, что таблица не пустая. По умолчанию True.

True

Raises:

Type Description
AssertionError

Если таблица пуста (при check_table_not_empty=True) или заголовки неверны.

Source code in components\table_component.py
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
def check_content(self,
                locator: str | Locator,
                expected_headers: list[str],
                check_table_not_empty: bool = True) -> None:
    """Проверяет содержимое таблицы.

    Проверяет заголовки и наличие данных в таблице.

    Args:
        locator: Локатор таблицы.
        expected_headers: Список ожидаемых заголовков таблицы.
        check_table_not_empty: Флаг проверки, что таблица не пустая.
                            По умолчанию True.

    Raises:
        AssertionError: Если таблица пуста (при check_table_not_empty=True)
                    или заголовки неверны.
    """

    table_content = self.read(locator)

    if len(table_content) == 0:
        assert False, "The contents of the table are missing"

    # Проверка заголовков таблицы
    self.check_table_headers(table_content[0], expected_headers)

    # Проверка наличия данных в таблице
    if len(table_content) == 1:
        if check_table_not_empty:
            assert False, "Table body is missing"
        else:
            logger.info("Таблица пустая (не содержит строк с данными)")

check_first_row_visibility(locator)

Проверяет видимость первой строки таблицы.

Parameters:

Name Type Description Default
locator str | Locator

Локатор таблицы.

required
Source code in components\table_component.py
257
258
259
260
261
262
263
264
265
266
def check_first_row_visibility(self, locator: str | Locator) -> None:
    """Проверяет видимость первой строки таблицы.

    Args:
        locator: Локатор таблицы.
    """

    table = self.get_locator(locator)
    first_row = table.locator("//tbody/tr").first
    expect(first_row).to_be_visible(), "The first table row is not visible"

check_last_row_visibility(locator)

Проверяет видимость последней строки таблицы.

Parameters:

Name Type Description Default
locator str | Locator

Локатор таблицы.

required
Source code in components\table_component.py
268
269
270
271
272
273
274
275
276
277
def check_last_row_visibility(self, locator: str | Locator) -> None:
    """Проверяет видимость последней строки таблицы.

    Args:
        locator: Локатор таблицы.
    """

    table = self.get_locator(locator)
    last_row = table.locator("//tbody/tr").last
    expect(last_row).to_be_visible(), "The last table row is not visible"

check_row_highlighting(locator, row_index)

Проверяет изменение цвета строки при наведении.

Parameters:

Name Type Description Default
locator str | Locator

Локатор таблицы.

required
row_index int

Индекс проверяемой строки.

required
Source code in components\table_component.py
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
def check_row_highlighting(self, locator: str | Locator, row_index: int) -> None:
    """Проверяет изменение цвета строки при наведении.

    Args:
        locator: Локатор таблицы.
        row_index: Индекс проверяемой строки.
    """

    table = self.get_locator(locator)
    row = table.locator("//tbody/tr").nth(row_index)

    row.scroll_into_view_if_needed()
    hover_element = row.locator(".body-row-hover")
    initial_color = hover_element.evaluate("el => window.getComputedStyle(el).backgroundColor")

    row.hover()
    self.page.wait_for_timeout(300)

    new_color = hover_element.evaluate("el => window.getComputedStyle(el).backgroundColor")
    assert initial_color != new_color, "Color of row did not change when hovering the cursor"

check_table_headers(actual_headers, expected_headers)

Проверка соответствия заголовка таблицы ожидаемому

Source code in components\table_component.py
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
def check_table_headers(self, actual_headers, expected_headers) -> None:
    """ Проверка соответствия заголовка таблицы ожидаемому"""

    is_equals = True

    arrow_state = ["keyboard_arrow_down", "keyboard_arrow_up"]

    for item in actual_headers:
        item = item.strip()

        if item in arrow_state:
            continue

        if item not in expected_headers:
            is_equals = False

    assert is_equals, \
        f"Expected events table headers {expected_headers} are not equal {actual_headers}"

click_arrow_button(table_locator, index)

Нажатие кнопки-стрелочки вверх/вниз в ячейке заголовка таблицы

Parameters:

Name Type Description Default
table_locator str | Locator

Локатор таблицы.

required
index int

Индекс ячейки в заголовке.

required
Source code in components\table_component.py
25
26
27
28
29
30
31
32
33
34
35
def click_arrow_button(self, table_locator: str | Locator, index: int) -> None:
    """ Нажатие кнопки-стрелочки вверх/вниз в ячейке заголовка таблицы

    Args:
        table_locator: Локатор таблицы.
        index: Индекс ячейки в заголовке.
    """

    arrow_button = self.get_header_cell_button(table_locator, index)
    assert arrow_button.is_enabled(), f"Arrow button is missing in {index} header cell"
    arrow_button.click()

datetime2timestamp(date_string)

Конвертация строкового представления даты и времени в Unix timestamp Args: date_string: Строка с датой и временем в формате d.m.Y H:M:S.

Returns:

Name Type Description
float float | None

Unix timestamp.

None float | None

конвертация невозможна

Source code in components\table_component.py
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
def datetime2timestamp(self, date_string: str) -> float|None:
    """ Конвертация строкового представления даты и времени в Unix timestamp
    Args:
        date_string: Строка с датой и временем в формате d.m.Y H:M:S.

    Returns:
        float: Unix timestamp.
        None: конвертация невозможна
    """

    # Формат, соответствующий строке с датой и временем
    format_string = "%d.%m.%Y %H:%M:%S"

    try:
        date_object = datetime.strptime(date_string, format_string)
        return date_object.timestamp()
    except ValueError:
        return None

get_arrow_button_state(table_locator, index)

Получение состояния кнопки-стрелочки вверх/вниз в ячейке заголовка таблицы

Parameters:

Name Type Description Default
table_locator str | Locator

Локатор таблицы.

required
index int

Индекс ячейки в заголовке.

required

Returns:

Type Description
str

up, если это стрелочка вверх. down, если это стрелочка вниз.

Source code in components\table_component.py
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
def get_arrow_button_state(self, table_locator: str | Locator, index: int) -> str:
    """ Получение состояния кнопки-стрелочки вверх/вниз в ячейке заголовка таблицы

    Args:
        table_locator: Локатор таблицы.
        index: Индекс ячейки в заголовке.

    Returns:
        up, если это стрелочка вверх. down, если это стрелочка вниз.
    """

    arrow_button = self.get_header_cell_button(table_locator, index)
    assert arrow_button.is_enabled(), f"Arrow button is missing in {index} header cell"

    state = arrow_button.inner_text()
    if state == "keyboard_arrow_up":
        return "up"
    elif state == "keyboard_arrow_down":
        return "down"
    else:
        assert False, f"Got unsupported arrow state: {state}"

get_header_cell_button(table_locator, index)

Поиск кнопки в ячейке заголовка таблицы

Parameters:

Name Type Description Default
table_locator str | Locator

Локатор таблицы.

required
index int

Индекс ячейки в заголовке.

required

Returns:

Type Description
Locator

Локатор строки кнопки.

Raises:

Type Description
AssertionError

Если индекс вне диапазона.

Source code in components\table_component.py
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
def get_header_cell_button(self, table_locator: str | Locator, index: int) -> Locator:
    """ Поиск кнопки в ячейке заголовка таблицы

    Args:
        table_locator: Локатор таблицы.
        index: Индекс ячейки в заголовке.

    Returns:
        Локатор строки кнопки.

    Raises:
        AssertionError: Если индекс вне диапазона.
    """

    table = self.get_locator(table_locator)
    header_cells_count = table.locator("//thead/tr/th").count()
    assert index in range(header_cells_count), "Header cell index is out of range"
    return table.locator("//thead/tr/th").nth(index).get_by_role("button")

get_row_locator(table_locator, row_index)

Возвращает локатор строки по индексу.

Parameters:

Name Type Description Default
table_locator str | Locator

Локатор таблицы.

required
row_index int

Индекс строки.

required

Returns:

Type Description
Locator | None

Локатор строки или None, если индекс вне диапазона.

Source code in components\table_component.py
 97
 98
 99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
def get_row_locator(self, table_locator: str | Locator, row_index: int) -> Locator | None:
    """Возвращает локатор строки по индексу.

    Args:
        table_locator: Локатор таблицы.
        row_index: Индекс строки.

    Returns:
        Локатор строки или None, если индекс вне диапазона.
    """

    table = self.get_locator(table_locator)

    rows = table.locator("//tbody/tr")

    if row_index in range(rows.count()):
        return rows.nth(row_index)
    else:
        return None

get_rows_count(locator)

Возвращает количество строк в таблице (без заголовка).

Returns:

Name Type Description
int int

Количество строк с данными.

Raises:

Type Description
AssertionError

Если таблица пуста.

Source code in components\table_component.py
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
def get_rows_count(self, locator: str | Locator) -> int:
    """Возвращает количество строк в таблице (без заголовка).

    Returns:
        int: Количество строк с данными.

    Raises:
        AssertionError: Если таблица пуста.
    """

    table_content = self.read(locator)
    rows_count = len(table_content)

    if rows_count == 0:
        assert False, "The contents of the table are missing"

    return rows_count - 1

read(locator)

Читает данные таблицы, включая заголовки.

Parameters:

Name Type Description Default
locator str | Locator

Локатор таблицы.

required

Returns:

Type Description
list[list[str]]

Двумерный список с данными таблицы.

Source code in components\table_component.py
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
def read(self, locator: str | Locator) -> list[list[str]]:
    """Читает данные таблицы, включая заголовки.

    Args:
        locator: Локатор таблицы.

    Returns:
        Двумерный список с данными таблицы.
    """

    table_data = []
    table = self.get_locator(locator)

    # Чтение заголовка таблицы
    header_cells = table.locator("//thead/tr")
    header_cell_text = header_cells.nth(0).inner_text()
    header_data = header_cell_text.split('\n')
    table_data.append(header_data)

    # Чтение ячеек таблицы
    rows = table.locator("//tbody/tr")
    for i in range(rows.count()):
        row = rows.nth(i)
        cells = row.locator("td")
        row_data = []
        for j in range(cells.count()):
            cell_text = cells.nth(j).inner_text()
            row_data.append(cell_text)
        table_data.append(row_data)

    return table_data