Browse Source

add: list_page
add: filter_page

dulip3ng 1 month ago
parent
commit
bea528b076

+ 21 - 0
common/condition_row.py

@@ -0,0 +1,21 @@
+#!/usr/bin/env python
+# -*- coding: utf-8 -*-
+'''
+@File    :   condition_row.py
+@Time    :   2024/11/21 16:42:00
+@Author  :   dulip3ng
+@Version :   1.0
+@Desc    :   None
+'''
+import typing
+from dataclasses import dataclass
+
+
+@dataclass
+class ConditionRow:
+
+    row: int
+    condition_name: str
+    condition_value: typing.Optional[str] = None
+    operator: typing.Optional[str] = None
+    logic: typing.Optional[str] = None

+ 51 - 0
pages/base_page.py

@@ -0,0 +1,51 @@
+#!/usr/bin/env python
+# -*- coding: utf-8 -*-
+'''
+@File    :   base_page.py
+@Time    :   2024/11/21 14:55:10
+@Author  :   dulip3ng
+@Version :   1.0
+@Desc    :   None
+'''
+
+from pages.locator.base_page_locator import BasePageLocator
+from playwright.sync_api import Page
+
+class BasePage:
+
+    def __init__(self, page: Page):
+        self.page = page
+
+    def click_blank(self):
+        self.page.locator(BasePageLocator.BLANK_LOC).click()
+
+    def get_error_message(self) -> str:
+        """
+        获取单一报错信息
+        :TODO: 未测试,暂不可用
+        :return: 报错信息
+        """
+        msg = self.page.locator(BasePageLocator.ERROR_MESSAGE_LOC).inner_html()
+        self.page.locator(BasePageLocator.ERROR_MSG_BUTTON_LOC).click()
+        return msg
+
+    def get_error_message_detail(self) -> str:
+        """
+        获取弹出框形式的多个报错信息
+        :TODO: 未测试,暂不可用
+        :return: 报错信息
+        """
+        detail_msg = self.page.locator(BasePageLocator.ERROR_MESSAGE_DETAIL_LOC).inner_html()
+        self.page.locator(BasePageLocator.ERROR_MSG_BUTTON_LOC).click()
+        return detail_msg
+
+    def get_warning_message(self, button_name:str) -> str:
+        """
+        获取单一提示信息
+        :TODO: 未测试,暂不可用
+        :param button_name:
+        :return: 提示信息
+        """
+        warning_msg = self.page.locator(BasePageLocator.WARNING_MESSAGE_LOC).inner_html()
+        self.page.locator(BasePageLocator.WARNING_MSG_BUTTON_ARGS_LOC % button_name).click()
+        return warning_msg

+ 19 - 20
pages/body_page.py

@@ -10,12 +10,11 @@
 
 from playwright.sync_api import Page, Error
 from pages.locator.body_page_locator import BodyPageLocator
+from pages.base_page import BasePage
 from common.field_type import FieldType
 
-class BodyPage:
+class BodyPage(BasePage):
 
-    def __init__(self, page: Page):
-        self.page = page
 
     def _click_blank(self):
         """
@@ -24,7 +23,7 @@ class BodyPage:
         """
         self.page.locator(BodyPageLocator.BLANK_LOC).click()
 
-    def change_sheet(self, sheet_name, order=1):
+    def change_sheet(self, sheet_name:str, order:int=1):
         """
         切换单据体页签
         :param sheet_name: 页面名称
@@ -33,7 +32,7 @@ class BodyPage:
         """
         self.page.locator(BodyPageLocator.SHEET_ARGS_LOC % (sheet_name, order)).click()
 
-    def click_button(self, button_name, sub_button_name=None):
+    def click_button(self, button_name:str, sub_button_name:str=None):
         """
         点击单据体按钮或子按钮
         如:新增行
@@ -48,7 +47,7 @@ class BodyPage:
             self.page.locator(BodyPageLocator.SUB_BUTTON_ARGS_LOC % sub_button_name).click()
         self._click_blank()
 
-    def click_line(self, row):
+    def click_line(self, row:int):
         """
         选中单据体行(通过点击行序号)
         :param row: 行号,从1开始
@@ -56,7 +55,7 @@ class BodyPage:
         """
         self.page.locator(BodyPageLocator.ACTIVATE_FIELD_ARGS_LOC % ("序号", row-1, 1)).click()
 
-    def _activate_field(self, field_name, row, order=1):
+    def _activate_field(self, field_name:str, row:int, order:int=1):
         """
         激活字段,单据体字段未激活状态时统一为span元素,无法识别字段类型,无法针对不同字段类型操作
         :param field_name: 字段名
@@ -66,7 +65,7 @@ class BodyPage:
         """
         self.page.locator(BodyPageLocator.ACTIVATE_FIELD_ARGS_LOC % (field_name, row-1, order)).click(force=True)
 
-    def _set_text(self, field_name, value, row, order=1):
+    def _set_text(self, field_name:str, value:str, row:int, order:int=1):
         """
         单据体文本字段赋值
         :param field_name: 字段名
@@ -79,7 +78,7 @@ class BodyPage:
         self.page.locator(BodyPageLocator.TEXT_FIELD_ARGS_LOC % (field_name, row-1, order)).fill(value)
         self._click_blank()
 
-    def _set_select(self, field_name, value, row, order=1):
+    def _set_select(self, field_name:str, value:str, row:int, order:int=1):
         """
         单据体下拉列表字段赋值
         :param field_name: 字段名
@@ -92,7 +91,7 @@ class BodyPage:
         self.page.locator(BodyPageLocator.SELECT_ITEM_ARGS_LOC % value).click()
         self._click_blank()
 
-    def _set_base(self, field_name, value, row, order=1):
+    def _set_base(self, field_name:str, value:str, row:int, order:int=1):
         """
         单据体基础资料类型字段赋值
         :param field_name: 字段名
@@ -108,7 +107,7 @@ class BodyPage:
         self.page.locator(BodyPageLocator.BASE_ITEM_ARGS_LOC % value).click()
         self._click_blank()
 
-    def set_value(self, field_name, value, row, order=1, field_type:FieldType = None):
+    def set_value(self, field_name:str, value:str, row:int, order:int=1, field_type:FieldType = None):
         """
         单据体字段赋值统一方法,根据给定或自动识别的字段类型调用不同方法完成赋值
         :param field_name: 字段名
@@ -129,7 +128,7 @@ class BodyPage:
             case FieldType.BASE:
                 self._set_base(field_name, value, row, order)
 
-    def _parse_field(self, field_name, order=1):
+    def _parse_field(self, field_name:str, order:int=1):
         """
         识别单据体字段类型
         :param field_name: 字段名
@@ -154,7 +153,7 @@ class BodyPage:
         """
         return self.page.locator(BodyPageLocator.ROW_COUNT_LOC).inner_text()
 
-    def get_value(self, field_name, row, order=1):
+    def get_value(self, field_name:str, row:int, order:int=1):
         """
         获取单据体字段值,不区分字段类型
         :param field_name: 字段名
@@ -164,7 +163,7 @@ class BodyPage:
         """
         return self.page.locator(BodyPageLocator.FIELD_VALUE_ARGS_LOC % (field_name, row-1, order)).inner_text()
 
-    def set_checked(self, field_name, row):
+    def set_checked(self, field_name:str, row:int):
         """
         勾选单据体复选框
         :param field_name: 字段名
@@ -174,7 +173,7 @@ class BodyPage:
         self.page.locator(BodyPageLocator.CHECKBOX_FIELD_ARGS_LOC % (field_name, row-1)).click()
         self._click_blank()
 
-    def set_text_in_body_head(self, field_name, value):
+    def set_text_in_body_head(self, field_name:str, value:str):
         """
         单据体中单据头形式文本字段赋值
         如:销售订单->物料数据->面料规格
@@ -185,7 +184,7 @@ class BodyPage:
         self.page.locator(BodyPageLocator.TEXT_FIELD_IN_BODY_HEAD_ARGS_LOC % field_name).fill(value)
         self._click_blank()
 
-    def set_select_in_body_head(self, field_name, value):
+    def set_select_in_body_head(self, field_name:str, value:str):
         """
         单据体中单据头形式下拉列表字段赋值
         如:销售订单->物料数据->货主类型
@@ -197,7 +196,7 @@ class BodyPage:
         self.page.locator(BodyPageLocator.SELECT_ITEM_ARGS_LOC % value).click()
         self._click_blank()
 
-    def set_base_in_body_head(self, field_name, value):
+    def set_base_in_body_head(self, field_name:str, value:str):
         """
         单据体中单据头形式基础资料字段赋值
         如:销售订单->物料数据->面料来源
@@ -211,7 +210,7 @@ class BodyPage:
         self.page.locator(BodyPageLocator.BASE_ITEM_ARGS_LOC % value).click()
         self._click_blank()
 
-    def get_text_in_body_head(self, field_name, order=1):
+    def get_text_in_body_head(self, field_name:str, order:int=1):
         """
         单据体中单据头形式文本字段取值
         如:销售订单->物料数据->面料规格
@@ -225,7 +224,7 @@ class BodyPage:
         except Error:
             return self.page.locator(BodyPageLocator.TEXT_VALUE_IN_BODY_HEAD_ARGS_LOC % (field_name, field_name, order)).inner_text()
 
-    def get_select_in_body_head(self, field_name, order=1):
+    def get_select_in_body_head(self, field_name:str, order:int=1):
         """
         单据体中单据头形式下拉列表字段取值
         如:销售订单->物料数据->货主类型
@@ -235,7 +234,7 @@ class BodyPage:
         """
         return self.page.locator(BodyPageLocator.SELECT_VALUE_IN_BODY_HEAD_ARGS_LOC % (field_name, order)).inner_text()
 
-    def get_base_in_body_head(self, field_name, order=1):
+    def get_base_in_body_head(self, field_name:str, order:int=1):
         """
         单据体中单据头形式基础资料字段取值
         如:销售订单->物料数据->面料来源

+ 367 - 0
pages/filter_page.py

@@ -0,0 +1,367 @@
+#!/usr/bin/env python
+# -*- coding: utf-8 -*-
+'''
+@File    :   filter_page.py
+@Time    :   2024/11/21 14:55:10
+@Author  :   dulip3ng
+@Version :   1.0
+@Desc    :   None
+'''
+import re
+import time
+
+from pages.locator.filter_page_locator import FilterPageLocator
+from playwright.sync_api import Page, expect
+from common.field_type import FieldType
+from common.condition_row import ConditionRow
+from pages.base_page import BasePage
+
+class FilterPage(BasePage):
+    """
+    过滤弹出框页面模型,适用于普通过滤弹出框,如在列表中点击过滤后弹出的过滤框
+    """
+
+    def check_all_org(self):
+        """
+        勾选所有组织复选框,如果已经是勾选状态则不操作
+
+        **用法:**
+
+        FilterPage.check_all_org()
+        :return:
+        """
+        # 多数情况下勾选所有组织是过滤界面第一个操作,偶发点击失效情况,加入2s等待时间确保稳定性
+        time.sleep(2)
+        locator_click = self.page.locator(FilterPageLocator.ALL_ORG_LOC)
+        locator_get_state = self.page.locator(FilterPageLocator.ALL_ORG_GET_CHECK_STATE_LOC)
+        expect(locator_click).to_be_enabled()
+        print(locator_get_state.is_checked())
+        if not locator_get_state.is_checked():
+            locator_click.click()
+
+    def uncheck_all_org(self):
+        """
+        取消勾选所有组织复选框,如果已经未勾选状态则不操作
+
+        **用法:**
+
+        FilterPage.uncheck_all_org()
+        :return:
+        """
+        # 多数情况下勾选所有组织是过滤界面第一个操作,偶发点击失效情况,加入2s等待时间确保稳定性
+        time.sleep(2)
+        locator_click = self.page.locator(FilterPageLocator.ALL_ORG_LOC)
+        locator_get_state = self.page.locator(FilterPageLocator.ALL_ORG_GET_CHECK_STATE_LOC)
+        expect(locator_click).to_be_enabled()
+        print(locator_get_state.is_checked())
+        if locator_get_state.is_checked():
+            print("22")
+            locator_click.click()
+
+    def check_org(self, *orgs:str):
+        """
+        根据给定组织编码或名称,勾选组织
+
+        **用法:**
+
+        FilterPage.check_org("100.1")
+
+        FilterPage.check_org("100.1", "鲁泰纺织股份")
+        :param orgs: 组织编码或名称,可传多个,模糊匹配
+        :return:
+        """
+        self.page.locator(FilterPageLocator.ORG_ARROW_LOC).click()
+        for org in orgs:
+            self.page.locator(FilterPageLocator.ORG_MULTI_ITEM_ARGS_LOC % org).click()
+        self.page.locator(FilterPageLocator.ORG_SELECT_OK_BTN_LOC).click()
+
+    def check_entity(self, *entitys_name:str):
+        """
+        根据给定实体,勾选实体
+
+        **用法:**
+
+        FilterPage.check_entity("明细信息")
+
+        FilterPage.check_entity("明细信息", "财务信息")
+
+        **注意:**
+
+        未进行勾选状态判断,未勾选状态下调用一次为勾选,再次调用为取消勾选
+        :param entitys_name: 实体名称
+        :return:
+        """
+        for entity in entitys_name:
+            self.page.locator(FilterPageLocator.ENTITY_CHECKBOX_ARGS_LOC % entity).click()
+
+    def click_tool_bar_button(self, button_name:str):
+        """
+        点击过滤框页面菜单行按钮
+
+        **用法:**
+
+        FilterPage.click_tool_bar_button("删除全部")
+        :param button_name: 按钮名称
+        :return:
+        """
+        self.page.locator(FilterPageLocator.TOOL_BAR_BTN_ARGS_LOC % button_name).click()
+
+    def set_condition(self, row:int, condition_name:str):
+        """
+        按行设置过滤条件项
+
+        **用法:**
+
+        FilterPage.set_condition(1, "基本信息-衬衣订单号")
+
+        **注意:**
+
+        部分单据中存在相同字段名在不同页签的情况,此时模糊匹配无法找到正确选项值,推荐使用条件项全称精确匹配
+        :param row: 行号,从1开始
+        :param condition_name: 条件项全称
+        :return:
+        """
+        self.click_tool_bar_button("新增行")
+        locator = self.page.locator(FilterPageLocator.FILTER_CONDITION_ARGS_LOC % ((row - 1) * 35))
+        locator.fill(condition_name)
+        # 条件项赋值后默认不弹出选项框,输入空格再删除,以便弹出选项框进行选择
+        locator.press_sequentially(" ")
+        locator.press("Backspace")
+        self.page.locator(FilterPageLocator.CONDITION_ITEM_ARGS_LOC % condition_name).click()
+
+    def set_operator(self, row:int, operator:str):
+        """
+        设置过滤条件操作符号
+
+        **用法:**
+
+        FilterPage.set_operator(1, "包含")
+        :param row: 行号
+        :param operator: 操作符名称
+        :return:
+        """
+        self.page.locator(FilterPageLocator.COMPARE_TYPE_ARGS_LOC % ((row - 1) * 35)).click()
+        self.page.locator(FilterPageLocator.COMPARE_ITEM_ARGS_LOC % operator).click()
+
+    def set_text(self, row:int, value:str):
+        """
+        条件值字段,文本字段类型赋值
+
+        **用法:**
+
+        FilterPage.set_text(1, "内销")
+
+        使用FilterPage.set_condition_value()方法代替,此方法无需区分字段类型
+        :param row: 行号,从1开始
+        :param value: 条件值
+        :return:
+        """
+        self.page.locator(FilterPageLocator.CONDITION_TEXT_FIELD_ARGS_LOC % ((row - 1) * 35)).fill(value)
+
+    def set_date(self, row:int, value:str):
+        """
+        条件值字段,日期字段类型赋值
+
+        **用法:**
+
+        FilterPage.set_date(1, "2024-11-25")
+
+        使用FilterPage.set_condition_value()方法代替,此方法无需区分字段类型
+        :param row: 行号,从1开始
+        :param value: 条件值,格式"2024-11-25"
+        :return:
+        """
+        self.page.locator(FilterPageLocator.CONDITION_DATE_FIELD_ARGS_LOC % ((row - 1) * 35)).fill(value)
+        self.click_blank()
+
+    def set_select(self, row:int, value:str):
+        """
+        条件值字段,下拉列表字段类型赋值
+
+        **用法:**
+
+        FilterPage.set_select(1, "创建")
+
+        使用FilterPage.set_condition_value()方法代替,此方法无需区分字段类型
+        :param row: 行号,从1开始
+        :param value: 条件值
+        :return:
+        """
+        self.page.locator(FilterPageLocator.CONDITION_SELECT_FIELD_ARGS_LOC % ((row - 1) * 35)).click()
+        self.page.locator(FilterPageLocator.CONDITION_SELECT_ITEM_ARGS_LOC % value).click()
+
+    def set_base(self, row:int, value:str):
+        """
+        条件值字段,基础资料字段类型赋值
+
+        **用法:**
+
+        FilterPage.set_base(1, "P010001")
+
+        使用FilterPage.set_condition_value()方法代替,此方法无需区分字段类型
+        :param row: 行号,从1开始
+        :param value: 条件值
+        :return:
+        """
+        # CONDITION_BASE_FIELD_ARGS_LOC input元素可操作性检查不通过,CONDITION_BASE_FIELD_SPAN_ARGS_LOC span劫持,这里直接对span进行点击
+        self.page.locator(FilterPageLocator.CONDITION_BASE_FIELD_SPAN_ARGS_LOC % ((row - 1) * 35)).click()
+        self.page.locator(FilterPageLocator.CONDITION_BASE_FIELD_ARGS_LOC % ((row - 1) * 35)).fill(value)
+        self.page.locator(FilterPageLocator.CONDITION_BASE_FIELD_ARGS_LOC % ((row - 1) * 35)).press_sequentially(" ")
+        self.page.locator(FilterPageLocator.CONDITION_BASE_ITEM_ARGS_LOC % value).press_sequentially(" ")
+
+    def set_text_area(self, row:int, value:str):
+        """
+        条件值字段,大文本字段类型赋值
+
+        **用法:**
+
+        FilterPage.set_text_area(1, "大文本字段值1234567890")
+
+        使用FilterPage.set_condition_value()方法代替,此方法无需区分字段类型
+        :param row: 行号,从1开始
+        :param value: 条件值
+        :return:
+        """
+        self.page.locator(FilterPageLocator.CONDITION_TEXTAREA_ARGS_LOC % ((row - 1) * 35)).fill(value)
+
+    def set_logic(self, row:int, value:str):
+        """
+        设置逻辑符号,如并且
+
+        **用法:**
+
+        FilterPage.set_logic(1, "并且")
+        :param row: 行号,从1开始
+        :param value: 逻辑符号,取并且,或者
+        :return:
+        """
+        self.page.locator(FilterPageLocator.LOGIC_ARGS_LOC % ((row - 1) * 35)).click()
+        self.page.locator(FilterPageLocator.COMPARE_ITEM_ARGS_LOC % value).click()
+
+    def _parse_condition_value_field_type(self, row:int) -> FieldType:
+        """
+        根据条件项元素属性,自动解析条件值项字段类型
+
+        **用法:**
+
+        FilterPage._parse_condition_value_field_type(1)
+
+        **注意:**
+
+        1.金蝶大版本升级后,判断依据可能会调整
+
+        2.当前支持v8.2
+        :param row: 行号,从1开始
+        :return:
+        """
+        data_role = self.page.locator(FilterPageLocator.PARSE_CONDITION_VALUE_FIELD_TYPE_ARGS_LOC % ((row - 1) * 35)).get_attribute("data-attdata")
+        pattern = "xtype\":\"([a-zA-Z0-9]+)"
+        match_strs = re.findall(pattern, data_role)
+        if match_strs:
+            match match_strs[0]:
+                case "textfield":
+                    return FieldType.TEXT
+                case "bosf7field":
+                    return FieldType.BASE
+                case "kdeditcombo" | "kdcombo":
+                    return FieldType.SELECT
+                case "kdmulticombo":
+                    return FieldType.MULTISELECT
+                case "kdtextarea" | "kdlangtextarea" | "kdtabpage":
+                    return FieldType.TEXTAREA
+                case "datefield":
+                    return FieldType.DATE
+        return FieldType.UNKNOWN
+    def set_condition_value(self, row:int, value:str, field_type: FieldType = None):
+        """
+        条件值字段赋值,不给定字段类型时自动解析字段类型,推荐不给定,方便统一调用形式
+
+        **用法:**
+
+        FilterPage.set_condition_value(1, "26024001")
+
+        FilterPage.set_condition_value(1, "26024001", FieldType.TEXT)
+
+        :param row: 行号,从1开始
+        :param value: 条件值
+        :param field_type: 条件值字段类型,可选
+        :return:
+        """
+        if field_type is None:
+            field_type = self._parse_condition_value_field_type(row)
+        match field_type:
+            case FieldType.TEXT:
+                self.set_text(row, value)
+            case FieldType.SELECT:
+                self.set_select(row, value)
+            case FieldType.BASE:
+                self.set_base(row, value)
+            case FieldType.DATE:
+                self.set_date(row, value)
+            case FieldType.TEXTAREA:
+                self.set_text_area(row, value)
+            case _:
+                raise TypeError("未匹配到过滤页面过滤条件输入框字段类型。")
+
+    def click_ok_button(self):
+        """
+        点击确定,开始过滤
+
+        **用法:**
+
+        FilterPage.click_ok_button()
+        :return:
+        """
+        self.page.locator(FilterPageLocator.FILTER_OK_BUTTON_LOC).click()
+
+    def set_condition_row(self, row:int, condition_name:str, condition_value:str=None, operator:str=None, logic:str=None):
+        """
+        设置一行过滤条件
+
+        **用法:**
+
+        FilterPage.set_condition_row(1, "创建日期", operator="今天")
+
+        FilterPage.set_condition_row(1, "衬衣订单号", "26024001")
+
+        FilterPage.set_condition_row(1, "衬衣订单号", "26024", "包含")
+
+        FilterPage.set_condition_row(1, "衬衣订单号", "26024", "包含", "或者")
+        :param row: 行号,从1,开始
+        :param condition_name: 过滤条件名
+        :param condition_value: 过滤条件值,可选
+        :param operator: 操作符,可选
+        :param logic: 逻辑符,可选
+        :return:
+        """
+        self.set_condition(row, condition_name)
+        if operator:
+            self.set_operator(row, operator)
+        if condition_value:
+            self.set_condition_value(row, condition_value)
+        if logic:
+            self.set_logic(row, logic)
+
+    def filter(self, *condition_row: ConditionRow):
+        """
+        根据条件对象进行过滤,默认勾选全部组织
+
+        **用法:**
+
+        cond1 = ConditionRow(1, "基本信息-衬衣订单号", "123456", "包含", "或者")
+
+        cond2 = ConditionRow(2, "单据状态", "已审核")
+
+        FilterPage.filter(cond1, cond2)
+        :param condition_row: 条件对象,ConditionRow(row:int, condition_name:str, condition_value:str=None, operator:str=None, logic:str=None), 可传多个
+        :return:
+        """
+        self.check_all_org()
+        self.click_tool_bar_button("全部删除")
+        time.sleep(1)
+        for cr in condition_row:
+            self.set_condition_row(cr.row, cr.condition_name, cr.condition_value, cr.operator, cr.logic)
+        self.click_ok_button()
+
+
+

+ 21 - 52
pages/head_page.py

@@ -9,15 +9,14 @@
 '''
 
 import re
-import time
 
 from playwright.sync_api import Page, expect, Error
+from pages.base_page import BasePage
 from pages.locator.head_page_locator import HeadPageLocator
 from common.field_type import FieldType
 
-class HeadPage:
-    def __init__(self, page: Page):
-        self.page = page
+class HeadPage(BasePage):
+
 
     def click_blank(self):
         """
@@ -26,7 +25,7 @@ class HeadPage:
         """
         self.page.locator(HeadPageLocator.BLANK_LOC).click()
 
-    def click_button(self, button_name, sub_button_name=None):
+    def click_button(self, button_name:str, sub_button_name:str=None):
         """
         点击单据头按钮
         :param button_name: 按钮名称
@@ -39,7 +38,7 @@ class HeadPage:
             self.page.locator(HeadPageLocator.BUTTON_ARROW_ARGS_LOC % button_name).hover()
             self.page.locator(HeadPageLocator.SUB_BUTTON_ARGS_LOC % sub_button_name).click()
 
-    def change_sheet(self, sheet_name):
+    def change_sheet(self, sheet_name:str):
         """
         点击单据头页签
         :param sheet_name: 页签名称
@@ -47,7 +46,7 @@ class HeadPage:
         """
         self.page.locator(HeadPageLocator.SHEET_ARGS_LOC % sheet_name).click()
 
-    def _set_text(self, field_name, value):
+    def _set_text(self, field_name:str, value:str):
         """
         单据头文本字段赋值
         :param field_name: 字段名
@@ -56,7 +55,7 @@ class HeadPage:
         """
         self.page.locator(HeadPageLocator.TEXT_FIELD_ARGS_LOC % field_name).fill(value)
 
-    def _set_base(self, field_name, value):
+    def _set_base(self, field_name:str, value:str):
         """
         单据头基础资料字段赋值
         :param field_name: 字段名
@@ -70,7 +69,7 @@ class HeadPage:
         self.page.locator(HeadPageLocator.BASE_FIELD_ARGS_LOC % field_name).press_sequentially(" ")
         self.page.locator(HeadPageLocator.BASE_ITEM_ARGS_LOC % value).click()
 
-    def _set_select(self, field_name, item_value):
+    def _set_select(self, field_name:str, item_value:str):
         """
         单据头下拉列表字段赋值
         :param field_name: 字段名
@@ -82,7 +81,7 @@ class HeadPage:
         self.page.locator(HeadPageLocator.SELECT_FIELD_ARGS_LOC % field_name).click(force=True)
         self.page.locator(HeadPageLocator.SELECT_ITEM_ARGS_LOC % item_value).click()
 
-    def _set_textarea(self, field_name, value):
+    def _set_textarea(self, field_name:str, value:str):
         """
         单据头大文本框字段赋值
         :param field_name: 字段名
@@ -91,7 +90,7 @@ class HeadPage:
         """
         self.page.locator(HeadPageLocator.TEXTAREA_FIELD_ARGS_LOC % field_name).fill(value)
 
-    def _set_multiselect(self, field_name, values):
+    def _set_multiselect(self, field_name:str, values:str):
         """
         单据头多选下拉列表字段赋值
         :param field_name: 字段名
@@ -103,7 +102,7 @@ class HeadPage:
             self.page.locator(HeadPageLocator.MULTISELECT_ITEM_ARGS_LOC % item_value).click()
         self.page.locator(HeadPageLocator.MULTISELECT_OK_BTN_LOC).click()
 
-    def set_value(self, field_name, *values, field_type: FieldType = None):
+    def set_value(self, field_name:str, *values:str, field_type: FieldType = None):
         """
         单据头字段赋值统一方法,根据给定或自动识别的字段类型调用不同方法完成赋值
         :param field_name: 字段名
@@ -126,7 +125,7 @@ class HeadPage:
             case FieldType.MULTISELECT:
                 self._set_multiselect(field_name, values)
 
-    def set_checked(self, field_name):
+    def set_checked(self, field_name:str):
         """
         单据头勾选复选框
         :param field_name: 复选框名
@@ -134,7 +133,7 @@ class HeadPage:
         """
         self.page.locator(HeadPageLocator.CHECKBOX_FIELD_ARGS_LOC % field_name).click()
 
-    def set_text_in_head_body(self, field_name, value, row):
+    def set_text_in_head_body(self, field_name:str, value:str, row:int):
         """
         单据头中的单据体文本字段赋值
         如样品通知单-接收人页签
@@ -146,7 +145,7 @@ class HeadPage:
         self.page.locator(HeadPageLocator.TRIGGER_IN_HEAD_BODY_FIELD_ARGS_LOC % (field_name, row-1)).click()
         self.page.locator(HeadPageLocator.TEXT_FIELD_IN_HEAD_BODY_ARGS_LOC % (field_name, row-1)).fill(value)
 
-    def set_base_in_head_body(self, field_name, value, row: int):
+    def set_base_in_head_body(self, field_name:str, value:str, row: int):
         """
         单据头中的单据体基础资料字段赋值
         如样品通知单-接收人页签
@@ -163,7 +162,7 @@ class HeadPage:
         self.page.locator(HeadPageLocator.BASE_ITEM_ARGS_LOC % value).click()
 
 
-    def _get_text_value(self, field_name) -> str:
+    def _get_text_value(self, field_name:str) -> str:
         """
         单据头文本字段取值
         文本字段有两种形式,一种是可编辑状态,值存放在input元素中,另一种是锁定状态值存放在span元素中,
@@ -180,7 +179,7 @@ class HeadPage:
             expect(locator_span).to_be_visible(timeout=500)
             return locator_span.inner_text()
 
-    def _get_select_value(self, field_name) -> str:
+    def _get_select_value(self, field_name:str) -> str:
         """
         单据头下拉列表字段取值
         下拉列表字段有两种形式,一种是可编辑状态,值存放在input元素中,另一种是锁定状态值存放在span元素中,
@@ -194,7 +193,7 @@ class HeadPage:
             return locator.input_value()
         except Error:
             return locator.inner_text()
-    def _get_base_value(self, field_name) -> str:
+    def _get_base_value(self, field_name:str) -> str:
         """
         获取单据头基础资料字段值
         :param field_name: 字段名
@@ -202,7 +201,7 @@ class HeadPage:
         """
         return self.page.locator(HeadPageLocator.BASE_VALUE_ARGS_LOC % field_name).inner_text()
 
-    def _get_textarea_value(self, field_name):
+    def _get_textarea_value(self, field_name:str):
         """
         单据头大文本字段取值
         :param field_name: 字段名
@@ -210,7 +209,7 @@ class HeadPage:
         """
         return self.page.locator(HeadPageLocator.TEXTAREA_FIELD_ARGS_LOC % field_name).input_value()
 
-    def _get_multiselect_value(self, field_name):
+    def _get_multiselect_value(self, field_name:str):
         """
         单据头多选下拉列表字段取值
         :param field_name: 字段名
@@ -218,7 +217,7 @@ class HeadPage:
         """
         return self.page.locator(HeadPageLocator.MULTISELECT_VALUE_ARGS_LOC % field_name).inner_text()
 
-    def get_value(self, field_name, field_type: FieldType=None) -> str:
+    def get_value(self, field_name:str, field_type: FieldType=None) -> str:
         """
         单据头字段取值统一方法,根据给定或自动识别的字段类型调用不同取值方法取值
         :param field_name: 字段名
@@ -242,38 +241,8 @@ class HeadPage:
             case _:
                 return ""
 
-    def get_error_message(self) -> str:
-        """
-        获取单一报错信息
-        :TODO: 未测试,暂不可用
-        :return: 报错信息
-        """
-        msg = self.page.locator(HeadPageLocator.ERROR_MESSAGE_LOC).inner_html()
-        self.page.locator(HeadPageLocator.ERROR_MSG_BUTTON_LOC).click()
-        return msg
-
-    def get_error_message_detail(self) -> str:
-        """
-        获取弹出框形式的多个报错信息
-        :TODO: 未测试,暂不可用
-        :return: 报错信息
-        """
-        detail_msg = self.page.locator(HeadPageLocator.ERROR_MESSAGE_DETAIL_LOC).inner_html()
-        self.page.locator(HeadPageLocator.ERROR_MSG_BUTTON_LOC).click()
-        return detail_msg
-
-    def get_warning_message(self, button_name) -> str:
-        """
-        获取单一提示信息
-        :TODO: 未测试,暂不可用
-        :param button_name:
-        :return: 提示信息
-        """
-        warning_msg = self.page.locator(HeadPageLocator.WARNING_MESSAGE_LOC).inner_html()
-        self.page.locator(HeadPageLocator.WARNING_MSG_BUTTON_ARGS_LOC % button_name).click()
-        return warning_msg
 
-    def parse_field_type(self, field_name) -> FieldType:
+    def parse_field_type(self, field_name:str) -> FieldType:
         """
         分析字段类型,set_value(), get_value()中调用的核心方法
         注意:金蝶版本升级后可能不同字段类型判断依据有变化,字段类型是根据元素"data-attdata"属性值判断,

+ 7 - 9
pages/home_page.py

@@ -7,16 +7,14 @@
 @Version :   1.0
 @Desc    :   None
 '''
-
+from pages.base_page import BasePage
 from pages.locator.home_page_locator import HomePageLocator
 from playwright.sync_api import Page, expect
 
 
-class HomePage:
-    def __init__(self, page: Page):
-        self.page = page
+class HomePage(BasePage):
 
-    def change_org(self, org_num):
+    def change_org(self, org_num:str):
         """
         切换组织
         :param org_num:组织代码
@@ -25,7 +23,7 @@ class HomePage:
         self.page.locator(HomePageLocator.ORGANIZATION_LOC).click()
         self.page.locator(HomePageLocator.ORG_ITEM_ARGS_LOC % org_num).click()
 
-    def search_bill(self, bill_name, order=1):
+    def search_bill(self, bill_name:str, order:int=1):
         """
         查询并打开单据
         :param bill_name:单据名称
@@ -36,7 +34,7 @@ class HomePage:
         self.page.locator(HomePageLocator.SEARCH_LOC).fill(bill_name)
         self.page.locator(HomePageLocator.BILL_LIST_ARGS_LOC % (bill_name, order)).click()
 
-    def close_bill_by_name(self, bill_name):
+    def close_bill_by_name(self, bill_name:str):
         """
         点击单据页签的"x"图标关闭单据
         :TODO: 加入关闭单据提示
@@ -54,7 +52,7 @@ class HomePage:
         self.page.locator(HomePageLocator.ALL_OPEN_FORM_BTN_LOC).click()
         self.page.locator(HomePageLocator.CLOSE_ALL_BILLS_ITEM_LOC).click()
 
-    def is_bill_opening(self, bill_name):
+    def is_bill_opening(self, bill_name:str):
         """
         判断单据是否是打开状态, 默认等待5秒
         :param bill_name:单据名称
@@ -67,7 +65,7 @@ class HomePage:
         except AssertionError:
             return False
 
-    def change_bill_tab(self, bill_name):
+    def change_bill_tab(self, bill_name:str):
         """
         在已打开单据间切换
         :param bill_name:单据名称

+ 181 - 0
pages/list_page.py

@@ -0,0 +1,181 @@
+#!/usr/bin/env python
+# -*- coding: utf-8 -*-
+'''
+@File    :   list_page.py
+@Time    :   2024/11/20 17:04:10
+@Author  :   dulip3ng 
+@Version :   1.0
+@Desc    :   None
+'''
+
+from pages.locator.list_page_locator import ListPageLocator
+from pages.base_page import BasePage
+from playwright.sync_api import Page, expect
+
+class ListPage(BasePage):
+    """
+    列表页面模型,适用于普通单据列表页面,如销售订单列表
+    """
+
+    def wait_list_loaded(self):
+        """
+        等待页面加载完成,当页面加载出第一行第一列表格时,停止等待
+
+        **用法:**
+
+        ListPage.wait_list_loaded()
+
+        **注意:**
+
+        1.有时表格加载完成但界面仍存在wait_loading转圈图标,不做处理由playwright自动处理后续元素可操作性检查
+
+        2.默认等待时间受 expect.set_options(timeout=)全局控制
+
+        3.此方法只能检测有数据的列表是否加载完成,因为无数据时不会出现td表格元素,无法区分是否加载完成
+        :return:
+        """
+        expect(self.page.locator(ListPageLocator.WAITING_LOC).first).to_be_visible()
+
+    def click_button(self, button_name: str, sub_button:str=None, grandson_button:str=None):
+        """
+        点击列表按钮,如“新增”
+
+        **用法:**
+
+        ListPage.click_button("新增")
+
+        ListPage.click_button("新增", "复制")
+
+        ListPage.click_button("选项", "套打", "套打设置")
+        :param button_name: 按钮名称
+        :param sub_button: 子按钮名称,如”复制“
+        :param grandson_button: 三级按钮名称,如”选项>套打>套打设置“
+        :return:
+        """
+        if sub_button is None and grandson_button is None:
+            self.page.locator(ListPageLocator.BUTTON_ARGS_LOC % (button_name, 1)).click()
+        if sub_button and grandson_button is None:
+            self.page.locator(ListPageLocator.BUTTON_ARGS_LOC % (button_name, 1)).hover()
+            self.page.locator(ListPageLocator.SUB_BUTTON_ARGS_LOC % sub_button).click()
+        if sub_button and grandson_button:
+            self.page.locator(ListPageLocator.BUTTON_ARGS_LOC % (button_name, 1)).hover()
+            self.page.locator(ListPageLocator.SUB_BUTTON_ARGS_LOC % sub_button).hover()
+            self.page.locator(ListPageLocator.GRANDSON_BUTTON_ARGS_LOC % (sub_button, grandson_button)).click()
+
+    def check_line_by_rows(self, *rows: int):
+        """
+        勾选给定行号
+
+        **用法:**
+
+        ListPage.check_line_by_rows(1)
+
+        ListPage.check_line_by_rows(1,2,3)
+
+        :param rows: 行号,可传多个
+        :return:
+        """
+        self.wait_list_loaded()
+        for r in rows:
+            self.page.locator(ListPageLocator.ROW_DATA_ARGS_LOC % r).click()
+
+    def open_bill_by_row(self, row: int):
+        """
+        根据行号打开单据
+
+        **用法:**
+
+        ListPage.open_bill_by_row(1)
+
+        **注意:**
+
+        此方法是通过点击行号的单据编号字段打开单据,确保该行单据编号字段有值
+        :param row: 行号
+        :return:
+        """
+        self.wait_list_loaded()
+        self.page.locator(ListPageLocator.BILL_NO_FIELD_ARGS_LOC % row).click()
+
+    def get_value(self, field_name:str, row: int):
+        """
+        获取列表字段值
+
+        **用法:**
+
+        ListPage.get_value("衬衣订单号", 1)
+
+        **注意:**
+
+        单据编号字段无效,可用get_bill_no()
+        :param field_name: 字段名
+        :param row: 行号
+        :return: 字段值
+        """
+        self.wait_list_loaded()
+        return self.page.locator(ListPageLocator.ROW_FIELD_VALUE_ARGS_LOC % (field_name, row)).inner_text()
+
+    def get_bill_no(self, row: int):
+        """
+        根据行号获取单据编号字段值
+
+        **用法:**
+
+        ListPage.get_bill_no(1)
+
+        **注意:**
+
+        确保给定行号单据编号字段有值
+        :param row: 行号
+        :return:
+        """
+        self.wait_list_loaded()
+        return self.page.locator(ListPageLocator.BILL_NO_FIELD_VALUE_ARGS_LOC % row).inner_text()
+
+    def get_row_count(self):
+        """
+        获取列表总行数
+
+        **用法:**
+
+        ListPage.get_row_count()
+        :return: 列表行数
+        """
+        self.wait_list_loaded()
+        return self.page.locator(ListPageLocator.LIST_ALL_LINE_LOC).count()
+
+    def check_line_by_field_value(self, field_name:str, value:str):
+        """
+        根据给定字段名,字段值勾选对应行
+
+        **用法:**
+
+        ListPage.check_line_by_field_value("衬衣订单号", "26022021")
+
+        **注意:**
+        当列表中存在多个相同字段值行时,只勾选第一行
+        :param field_name: 字段名
+        :param value: 字段值
+        :return:
+        """
+        self.wait_list_loaded()
+        row = self.page.locator(ListPageLocator.FIELDS_VALUE_ARGS_LOC % (field_name, value)).first.get_attribute('data-rowid')
+        self.check_line_by_rows(int(row))
+
+    def field_filter(self, field_name:str, value:str):
+        """
+        字段过滤,点击列表列名右侧小漏斗,进行列表简单过滤
+
+        **用法:**
+
+        ListPage.field_filter("销售类别", "内销")
+        :param field_name: 字段名
+        :param value: 过滤值
+        :return:
+        """
+        self.wait_list_loaded()
+        self.page.locator(ListPageLocator.FIELD_FILTER_ARGS_LOC % field_name).hover()
+        self.page.locator(ListPageLocator.FILTER_ICON_ARGS_LOC % field_name).click()
+        self.page.locator(ListPageLocator.FILTER_VALUE_ARGS_LOC % value).click()
+        self.page.locator(ListPageLocator.FILTER_OK_BTN).click()
+
+

+ 20 - 0
pages/locator/base_page_locator.py

@@ -0,0 +1,20 @@
+#!/usr/bin/env python
+# -*- coding: utf-8 -*-
+'''
+@File    :   base_page_locator.py
+@Time    :   2024/11/21 14:57:52
+@Author  :   dulip3ng
+@Version :   1.0
+@Desc    :   None
+'''
+
+class BasePageLocator:
+
+    #登录后页面上方账套名称显示位置
+    BLANK_LOC = "//a[contains(@id, '-FBTNDATACENTER-FBTNDATACENTER')]"
+    #错误信息相关定位
+    ERROR_MESSAGE_LOC = "//*[contains(@id,'-POPFLOATEERRMESSAGE')]/div[1]/span"
+    ERROR_MSG_BUTTON_LOC = "//*[contains(@id,'-POPFLOATEERRMESSAGE')]/div/button"
+    ERROR_MESSAGE_DETAIL_LOC = "//span[@class = 'kd-msg-details']"
+    WARNING_MESSAGE_LOC = "//*[contains(@id,'-POPFLOATEMESSAGE')]/div[1]/span"
+    WARNING_MSG_BUTTON_ARGS_LOC = "//div[contains(@id,'-POPFLOATEMESSAGE')]/div[contains(@id, 'btn')]/button[text() = '%s']"

+ 37 - 0
pages/locator/filter_page_locator.py

@@ -0,0 +1,37 @@
+#!/usr/bin/env python
+# -*- coding: utf-8 -*-
+'''
+@File    :   filter_page_locator.py
+@Time    :   2024/11/21 14:51:52
+@Author  :   dulip3ng
+@Version :   1.0
+@Desc    :   None
+'''
+
+class FilterPageLocator:
+
+    ALL_ORG_GET_CHECK_STATE_LOC = "//div[@class= 'k-widget k-window' and @data-role='draggable' and not(contains(@style, 'display: none'))]//div[contains(@id, '_Filter-FTAB_c-') and @class='k-content k-state-active']//div[contains(@data-kdid, '_Filter-FPANEL3_top')]//label[text() = '所有组织']/preceding-sibling::input";
+    ALL_ORG_LOC = "//div[@class= 'k-widget k-window' and @data-role='draggable' and not(contains(@style, 'display: none'))]//div[contains(@id, '_Filter-FTAB_c-') and @class='k-content k-state-active']//div[contains(@data-kdid, '_Filter-FPANEL3_top')]//label[text() = '所有组织']";
+    ORG_ARROW_LOC = "//div[@class= 'k-widget k-window' and @data-role='draggable' and not(contains(@style, 'display: none'))]//div[contains(@id, '_Filter-FTAB_c-') and @class='k-content k-state-active']//div[contains(@data-kdid, '_Filter-FPANEL3_top')]//span[@class = 'k-icon k-i-arrow-s' and text() = 'select']";
+    ORG_MULTI_ITEM_ARGS_LOC = "//div[@class = 'k-animation-container' and contains(@style, 'display: block')]/descendant::button[contains(text(), '%s')]";
+    ORG_SELECT_OK_BTN_LOC = "//div[@class = 'k-animation-container' and contains(@style, 'display: block')]/descendant::div[text() = '确定']";
+    ENTITY_CHECKBOX_ARGS_LOC = "//div[@class= 'k-widget k-window' and @data-role='draggable' and not(contains(@style, 'display: none'))]//div[contains(@id, '_Filter-FTAB_c-') and @class='k-content k-state-active']//div[contains(@data-kdid, '_Filter-FPANEL3_fill')]//label[contains(@id, '_checkbox') and text() = '%s']";
+    TOOL_BAR_BTN_ARGS_LOC = "//div[@class= 'k-widget k-window' and @data-role='draggable' and not(contains(@style, 'display: none'))]//div[contains(@id, '_Filter-FTAB_c-') and @class='k-content k-state-active']//li[text() = '%s']";
+    FILTER_OK_BUTTON_LOC = "//div[@class= 'k-widget k-window' and @data-role='draggable' and not(contains(@style, 'display: none'))]//div[contains(@id, '_Filter-FBTNOK')]/a";
+    FILTER_CONDITION_ARGS_LOC = "//div[@class= 'k-widget k-window' and @data-role='draggable' and not(contains(@style, 'display: none'))]//div[contains(@id, '_Filter-FTAB_c-') and @class='k-content k-state-active']//div[contains(@id, '_row_Add') and contains(@style, '; top: %dpx')]//td[contains(@id, '_row_Add_PART_Fileds_P')]//input[contains(@class, 'k-input defaultQuikerRowEmptyShow')]";
+
+    #过滤页面条件行相关定位,条件行使用35px的倍数确定第几行,如0px=第一行,35px=第二行
+    CONDITION_ITEM_ARGS_LOC = "//div[@class= 'k-animation-container']//div[@class='k-list-scroller']/ul[@aria-hidden='false']//li/span[substring-after(text(), '.')='' and contains(text(), '%s')]";
+    COMPARE_TYPE_ARGS_LOC = "//div[@class= 'k-widget k-window' and @data-role='draggable' and not(contains(@style, 'display: none'))]//div[contains(@id, '_Filter-FTAB_c-') and @class='k-content k-state-active']//div[contains(@id, '_row_Add') and contains(@style, '; top: %dpx')]//td[contains(@id, '_row_Add_PPART_CompareType_P')]";
+    COMPARE_ITEM_ARGS_LOC = "//div[@class = 'k-animation-container' and contains(@style, 'display: block')]/div/div[2]/ul/li[text() = '%s']";
+    CONDITION_TEXT_FIELD_ARGS_LOC = "//div[@class= 'k-widget k-window' and @data-role='draggable' and not(contains(@style, 'display: none'))]//div[contains(@id, '_Filter-FTAB_c-') and @class='k-content k-state-active']//div[contains(@id, '_row_Add') and contains(@style, '; top: %dpx')]//td[@valign='top']//input[@class='k-textbox']";
+    CONDITION_DATE_FIELD_ARGS_LOC = "//div[@class= 'k-widget k-window' and @data-role='draggable' and not(contains(@style, 'display: none'))]//div[contains(@id, '_Filter-FTAB_c-') and @class='k-content k-state-active']//div[contains(@id, '_row_Add') and contains(@style, '; top: %dpx')]//td[@valign='top']//input[@class='k-input' and @type = 'text']";
+    CONDITION_SELECT_FIELD_ARGS_LOC = "//div[@class= 'k-widget k-window' and @data-role='draggable' and not(contains(@style, 'display: none'))]//div[contains(@id, '_Filter-FTAB_c-') and @class='k-content k-state-active']//div[contains(@id, '_row_Add') and contains(@style, '; top: %dpx')]//td[@valign='top']//span[@class='k-input']";
+    CONDITION_TEXTAREA_ARGS_LOC = "//div[@class= 'k-widget k-window' and @data-role='draggable' and not(contains(@style, 'display: none'))]//div[contains(@id, '_Filter-FTAB_c-') and @class='k-content k-state-active']//div[contains(@id, '_row_Add') and contains(@style, '; top: %dpx')]//td[@valign='top']//textarea[@class='k-textbox']";
+    CONDITION_BASE_FIELD_SPAN_ARGS_LOC = "//div[@class= 'k-widget k-window' and @data-role='draggable' and not(contains(@style, 'display: none'))]//div[contains(@id, '_Filter-FTAB_c-') and @class='k-content k-state-active']//div[contains(@id, '_row_Add') and contains(@style, '; top: %dpx')]//td[@valign='top']//span[@class = 'k-select kd-editor-btn']/following-sibling::input[@class='k-input']/following-sibling::span"
+    CONDITION_BASE_FIELD_ARGS_LOC = "//div[@class= 'k-widget k-window' and @data-role='draggable' and not(contains(@style, 'display: none'))]//div[contains(@id, '_Filter-FTAB_c-') and @class='k-content k-state-active']//div[contains(@id, '_row_Add') and contains(@style, '; top: %dpx')]//td[@valign='top']//span[@class = 'k-select kd-editor-btn']/following-sibling::input[@class='k-input']"
+    CONDITION_SELECT_ITEM_ARGS_LOC = "//div[@class='k-animation-container' and contains(@style, 'display: block')]//ul[@class='k-list k-reset']//li/span[text()='%s']";
+    CONDITION_BASE_ITEM_ARGS_LOC = "//div[@class = 'k-animation-container' and contains(@style, 'display: block')]//table[@class = 'k-selectable']/descendant::td[text() = '%s']";
+    LOGIC_ARGS_LOC = "//div[@class= 'k-widget k-window' and @data-role='draggable' and not(contains(@style, 'display: none'))]//div[contains(@id, '_Filter-FTAB_c-') and @class='k-content k-state-active']//div[contains(@id, '_row_Add') and contains(@style, '; top: %dpx')]//td[contains(@id, '_row_Add_PART_Logic_P')]";
+
+    PARSE_CONDITION_VALUE_FIELD_TYPE_ARGS_LOC = "//div[@class= 'k-widget k-window' and @data-role='draggable' and not(contains(@style, 'display: none'))]//div[contains(@id, '_Filter-FTAB_c-') and @class='k-content k-state-active']//div[contains(@id, '_row_Add') and contains(@style, '; top: %dpx')]//td[@valign='top']/div/div"

+ 1 - 6
pages/locator/head_page_locator.py

@@ -31,12 +31,7 @@ class HeadPageLocator:
     IS_SELECT_FIELD_ARGS_LOC = "//div[contains(@id, '-FMAINTAB_c-') and @class='k-content k-state-active']//div[contains(@id, 'SPLITECONTAINER') and @splitter='first']//div[contains(@id, 'TAB') and @class = 'k-content k-state-active']//span[translate(text(), ' ', '') = translate('%s', ' ', '')]/parent::div/following-sibling::div[1]//span[@class = 'k-icon k-i-arrow-s']"
     IS_BASE_FIELD_ARGS_LOC = "//div[contains(@id, '-FMAINTAB_c-') and @class='k-content k-state-active']//div[contains(@id, 'SPLITECONTAINER') and @splitter='first']//div[contains(@id, 'TAB') and @class = 'k-content k-state-active']//span[translate(text(), ' ', '') = translate('%s', ' ', '')]/parent::div/following-sibling::div[1]//span[@class = 'k-icon k-i-search']"
     PARSE_FIELD_TYPE_ARGS_LOC = "//div[contains(@id, '-FMAINTAB_c-') and @class='k-content k-state-active']//div[contains(@id, 'SPLITECONTAINER') and @splitter='first']//div[contains(@id, 'TAB') and @class = 'k-content k-state-active']//span[@role='kdspanlabel' and translate(@title, ' ', '') = translate('%s', ' ', '')]/parent::div/following-sibling::div[contains(@class, 'kdItemContainer_editorct')]/div"
-    #错误信息相关定位
-    ERROR_MESSAGE_LOC = ".//*[contains(@id,'-POPFLOATEERRMESSAGE')]/div[1]/span"
-    ERROR_MSG_BUTTON_LOC = "//*[contains(@id,'-POPFLOATEERRMESSAGE')]/div[3]/button[1]"
-    ERROR_MESSAGE_DETAIL_LOC = ".//span[@class = 'kd-msg-details']"
-    WARNING_MESSAGE_LOC = ".//*[contains(@id,'-POPFLOATEMESSAGE')]/div[1]/span"
-    WARNING_MSG_BUTTON_ARGS_LOC = "//div[contains(@id,'-POPFLOATEMESSAGE')]/div[contains(@id, 'btn')]/button[text() = '%s']"
+
     #审核印章
     CHECKED_IMAGE_LOC = "//img[@src = '.././images/biz/default/Common/WaterMarks/approved_2052.png' and contains(@style, 'display: block')]"
     

+ 29 - 0
pages/locator/list_page_locator.py

@@ -0,0 +1,29 @@
+#!/usr/bin/env python
+# -*- coding: utf-8 -*-
+'''
+@File    :   list_page_locator.py
+@Time    :   2024/11/20 17:03:52
+@Author  :   dulip3ng 
+@Version :   1.0
+@Desc    :   None
+'''
+
+class ListPageLocator:
+
+    BUTTON_ARGS_LOC = "(//div[contains(@id, '-FMAINTAB_c-') and @class='k-content k-state-active']//div[contains(@id, '-BILLMENU_TOOLBAR')]/ul/li[contains(@class, 'k-item k-state-default kdmenuitem') and not(contains(@class, 'kd-menubtn-disabled'))]/span/span[text()='%s'])[%d]";
+    SUB_BUTTON_ARGS_LOC = "//div[@class = 'k-animation-container' and contains(@style, 'display: block')]/descendant::span[contains(@id, 'BILLMENU_TOOLBAR') and text() = '%s']";
+    GRANDSON_BUTTON_ARGS_LOC = "//div[@class = 'k-animation-container' and contains(@style, 'display: block')]/descendant::span[contains(@id, 'BILLMENU_TOOLBAR') and text() = '%s']/../../descendant::span[@title = '%s']";
+    WAITING_LOC = "//div[contains(@id, '-FMAINTAB_c-') and @class='k-content k-state-active']//div[substring(@id,string-length(@id)-14)='FLIST-gridPanel']/descendant::tbody[@role = 'rowgroup']/tr/td";
+    ROW_DATA_ARGS_LOC = "(//div[contains(@id, '-FMAINTAB_c-') and @class='k-content k-state-active']//div[substring(@id,string-length(@id)-14)='FLIST-gridPanel']/descendant::tbody[@role = 'rowgroup']/tr/td/div/input/parent::div)[%d]";
+    ALL_LINE_CHECKBOX_LOC = "//div[contains(@id, '-FMAINTAB_c-') and @class='k-content k-state-active']//div[substring(@id,string-length(@id)-14)='FLIST-gridPanel']/descendant::div[@class='k-grid-header-wrap k-auto-scrollable']/table/thead/tr/th//span[@class='kd-grid-selector']";
+    BILL_NO_FIELD_ARGS_LOC = "//div[contains(@id, '-FMAINTAB_c-') and @class='k-content k-state-active']//td/span[@data-field=//th[@data-title='单据编号']/@data-field and @data-rowid = '%d']";
+    ROW_FIELD_VALUE_ARGS_LOC = "//div[contains(@id, '-FMAINTAB_c-') and @class='k-content k-state-active']//div[contains(@id, '-FLIST-c')]//span[@data-field=//th[@data-title='%s']/@data-field and @data-rowid='%d']";
+    BILL_NO_FIELD_VALUE_ARGS_LOC = "//div[contains(@id, '-FMAINTAB_c-') and @class='k-content k-state-active']//div[contains(@id, '-FLIST-c')]//span[@data-field=//th[@data-title='单据编号']/@data-field and @data-rowid='%d']/a";
+    LIST_ALL_LINE_LOC = "//div[contains(@id, '-FMAINTAB_c-') and @class='k-content k-state-active']//div[substring(@id,string-length(@id)-14)='FLIST-gridPanel']/descendant::tbody[@role = 'rowgroup']/tr/td/div[@class = 'kd-grid-celldiv kd-grid-selector ']";
+    FIELDS_VALUE_ARGS_LOC = "//div[contains(@id, '-FMAINTAB_c-') and @class='k-content k-state-active']//div[contains(@id, '-FLIST-c')]//span[@data-field=//th[@data-title='%s']/@data-field and text() = '%s']";
+    FIELD_FILTER_ARGS_LOC = "//div[contains(@id, '-FMAINTAB_c-') and @class='k-content k-state-active']//div[contains(@id, '-FLIST-c')]//th[@data-title='%s']";
+    FILTER_ICON_ARGS_LOC = "//div[contains(@id, '-FMAINTAB_c-') and @class='k-content k-state-active']//div[contains(@id, '-FLIST-c')]//th[@data-title='%s']/a/span[@class = 'k-icon k-i-filter']";
+    FILTER_VALUE_ARGS_LOC = "//div[@class = 'k-animation-container kd-grid-filterMenu' and contains(@style, 'display: block')]/form/ul/li/label[text() = '%s']";
+    FILTER_OK_BTN = "//div[@class = 'k-animation-container kd-grid-filterMenu' and contains(@style, 'display: block')]/form/button[text() = '确定']";
+
+    ROW_DATA_PUSH_LOC = "(//div[contains(@id, '-FMAINTAB_c-') and @class='k-content k-state-active']//div[contains(@id,'FRESULTLIST-gridPane')]/descendant::tbody[@role = 'rowgroup']/tr)[%d]";

+ 6 - 7
pages/login_page.py

@@ -9,14 +9,13 @@
 '''
 import time
 
+from pages.base_page import BasePage
 from pages.locator.login_page_loctor import LoginPageLocator
 from playwright.sync_api import Page, expect
 
-class LoginPage:
-    def __init__(self, page: Page):
-        self.page = page
+class LoginPage(BasePage):
 
-    def _choice_db(self, db):
+    def _choice_db(self, db:str):
         """
         设置账套
         :param db: 账套名
@@ -31,7 +30,7 @@ class LoginPage:
             return
         self.page.locator(LoginPageLocator.DB_SELECT_ITEM_ARGS_LOC % db).click()
 
-    def _set_username(self, username):
+    def _set_username(self, username:str):
         """
         设置用户名
         :param username: 用户名
@@ -39,7 +38,7 @@ class LoginPage:
         """
         self.page.locator(LoginPageLocator.USERNAME_LOC).fill(username)
 
-    def _set_password(self, password):
+    def _set_password(self, password:str):
         """
         设置密码
         :param password: 密码
@@ -66,7 +65,7 @@ class LoginPage:
         except AssertionError:
             pass
 
-    def login(self, db, username, password):
+    def login(self, db:str, username:str, password:str):
         """
         系统登录
         :param db: 账套名

+ 9 - 1
pages/nebula.py

@@ -13,6 +13,8 @@ from pages.login_page import LoginPage
 from pages.home_page import HomePage
 from pages.head_page import HeadPage
 from pages.body_page import BodyPage
+from pages.list_page import ListPage
+from pages.filter_page import FilterPage
 
 def new_login_page(page: Page):
     return LoginPage(page)
@@ -24,4 +26,10 @@ def new_head_page(page: Page):
     return HeadPage(page)
 
 def new_body_page(page: Page):
-    return BodyPage(page)
+    return BodyPage(page)
+
+def new_list_page(page: Page):
+    return ListPage(page)
+
+def new_filter_page(page: Page):
+    return FilterPage(page)

+ 14 - 3
tests/page_test/conftest.py

@@ -11,7 +11,7 @@ import pytest
 from playwright.sync_api import sync_playwright, expect, Page
 from pages import nebula
 
-expect.set_options(timeout=15_000)
+expect.set_options(timeout=30_000)
 
 @pytest.fixture(scope="module")
 def browser():
@@ -24,8 +24,8 @@ def browser():
 @pytest.fixture(scope="module")
 def page(browser):
     # 为每个测试用例提供一个新的页面实例
-    context = browser.new_context()
-    page = browser.new_page()
+    context = browser.new_context(viewport= {"width": 1920, "height": 1080})
+    page = context.new_page()
     yield page
     page.close()
     context.close()
@@ -42,3 +42,14 @@ def home_page(page):
 def login_page(page):
     return nebula.new_login_page(page)
 
+@pytest.fixture(scope="module")
+def list_page(page):
+    return nebula.new_list_page(page)
+
+@pytest.fixture(scope="module")
+def head_page(page):
+    return nebula.new_head_page(page)
+
+@pytest.fixture(scope="module")
+def filter_page(page):
+    return nebula.new_filter_page(page)

+ 0 - 1
tests/page_test/test_body_page.py

@@ -7,7 +7,6 @@
 @Version :   1.0
 @Desc    :   None
 '''
-import time
 
 import pytest
 from playwright.sync_api import Page

+ 43 - 0
tests/page_test/test_filter_page.py

@@ -0,0 +1,43 @@
+#!/usr/bin/env python
+# -*- coding: utf-8 -*-
+'''
+@File    :   login_page.py
+@Time    :   2024/11/22 17:34:19
+@Author  :   dulip3ng
+@Version :   1.0
+@Desc    :   None
+'''
+import time
+
+import pytest
+from playwright.sync_api import Page
+from common.condition_row import ConditionRow
+
+@pytest.fixture(scope="module", autouse=True)
+def open_bill(page: Page, login_page, home_page, list_page):
+    page.goto("http://10.200.0.222/k3cloud/html5/")
+    login_page.login('正式数据库', '杜立朋', 'lt_k31233')
+    home_page.search_bill("销售订单列表")
+
+def test_set_condition_value(page: Page, filter_page, list_page):
+    list_page.click_button("过滤")
+    cond1 = ConditionRow(1, "基本信息-衬衣订单号", "123456")
+    cond2 = ConditionRow(2, "基本信息-销售类别", "内销")
+    cond3 = ConditionRow(3, "基本信息-单据状态", "创建")
+    cond4 = ConditionRow(4, "基本信息-备注", "123456")
+    cond5 = ConditionRow(5, "基本信息-创建日期", "2024-11-01")
+    filter_page.filter(cond1, cond2, cond3, cond4, cond5)
+
+def test_check_entity(page: Page, filter_page, list_page):
+    time.sleep(2)
+    list_page.click_button("过滤")
+    filter_page.check_entity("订单条款", "交货明细")
+
+def test_check_org(page: Page, filter_page):
+    filter_page.uncheck_all_org()
+    filter_page.check_org("100.1", "100.2")
+
+def test_set_operator_logic(page: Page, filter_page):
+    cond1 = ConditionRow(1, "基本信息-衬衣订单号", "123456", operator="包含", logic="或者")
+    filter_page.filter(cond1)
+

+ 48 - 0
tests/page_test/test_list_page.py

@@ -0,0 +1,48 @@
+#!/usr/bin/env python
+# -*- coding: utf-8 -*-
+'''
+@File    :   login_page.py
+@Time    :   2024/11/21 08:56:19
+@Author  :   dulip3ng
+@Version :   1.0
+@Desc    :   None
+'''
+
+import pytest
+from playwright.sync_api import Page
+
+@pytest.fixture(scope="module", autouse=True)
+def open_bill(page: Page, login_page, home_page):
+    page.goto("http://10.201.3.146/k3cloud/html5/")
+    login_page.login('LTTC1103', '杜立朋', 'lt_k31233')
+    home_page.search_bill("销售订单列表")
+
+def test_check_line_by_rows(page, list_page):
+    list_page.check_line_by_rows(1)
+    list_page.check_line_by_rows(2,3)
+
+def test_get_value(page, list_page):
+    value = list_page.get_value("衬衣订单号", 1)
+    assert value is not None
+
+def test_get_bill_no(page, list_page):
+    value = list_page.get_bill_no(1)
+    assert value is not None
+
+def test_get_row_count(page, list_page):
+    value = list_page.get_row_count()
+    assert value is not None
+
+def test_field_filter(page, list_page):
+    list_page.field_filter("销售类别", "外销")
+
+def test_check_line_by_field_value(page, list_page):
+    list_page.check_line_by_field_value("销售类别", "外销")
+
+def test_open_bill_by_row(page, list_page):
+    list_page.open_bill_by_row(1)
+
+def test_click_button(page, list_page, head_page, home_page):
+    home_page.change_bill_tab("销售订单列表")
+    list_page.click_button("刷新")
+    list_page.click_button("业务查询", "预收查询")