python-qlv-helper 0.6.0__py3-none-any.whl → 0.9.3__py3-none-any.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -12,6 +12,7 @@
12
12
  import re
13
13
  import json
14
14
  import aiohttp
15
+ from random import random
15
16
  from datetime import datetime
16
17
  from urllib.parse import quote
17
18
  from bs4 import BeautifulSoup, Tag
@@ -20,6 +21,7 @@ from typing import Dict, Any, Optional, List
20
21
  from qlv_helper.utils.type_utils import convert_cn_to_en
21
22
  from http_helper.client.async_proxy import HttpClientFactory
22
23
  from qlv_helper.utils.datetime_utils import get_current_dtstr
24
+ from flight_helper.models.dto.procurement import FillProcurementInputDTO
23
25
  from qlv_helper.utils.type_utils import get_key_by_index, get_value_by_index, safe_convert_advanced
24
26
 
25
27
 
@@ -43,39 +45,52 @@ async def get_order_page_html(
43
45
  )
44
46
 
45
47
 
46
- async def fill_procurement_info_with_http(
47
- *, order_id: int, qlv_domain: str, amount: float, pre_order_id: str, platform_user_id: str, user_password: str,
48
- passengers: List[str], fids: str, pids: List[str], transaction_id: str, qlv_protocol: str = "http",
49
- retry: int = 1, timeout: int = 5, enable_log: bool = True, cookie_jar: Optional[aiohttp.CookieJar] = None,
50
- playwright_state: Dict[str, Any] = None, data_list: Optional[List[Dict[str, Any]]] = None
48
+ async def fill_procurement_dto_with_http(
49
+ *, fill_procurement_dto: FillProcurementInputDTO, retry: int = 1, timeout: int = 5, enable_log: bool = True,
50
+ cookie_jar: Optional[aiohttp.CookieJar] = None, playwright_state: Dict[str, Any] = None,
51
+ data_list: Optional[List[Dict[str, Any]]] = None
51
52
  ) -> Dict[str, Any]:
52
53
  client = HttpClientFactory(
53
- protocol=qlv_protocol, domain=qlv_domain, timeout=timeout, enable_log=enable_log, retry=retry,
54
- cookie_jar=cookie_jar or aiohttp.CookieJar(),
55
- playwright_state=playwright_state
54
+ protocol=fill_procurement_dto.pl_protocol, domain=fill_procurement_dto.pl_domain, timeout=timeout, retry=retry,
55
+ enable_log=enable_log, cookie_jar=cookie_jar or aiohttp.CookieJar(), playwright_state=playwright_state
56
56
  )
57
57
 
58
58
  headers = {
59
59
  "Content-Type": "application/x-www-form-urlencoded; charset=UTF-8",
60
- "Referer": f"{qlv_protocol}://{qlv_domain}/OrderProcessing/NewTicket/{order_id}?&r={datetime.now().strftime("%Y%m%d%H%M%S")}",
60
+ "Referer": f"{fill_procurement_dto.pl_protocol}://{fill_procurement_dto.pl_domain}/OrderProcessing/NewTicket/{fill_procurement_dto.order_no}?&r={datetime.now().strftime("%Y%m%d%H%M%S")}",
61
61
  }
62
62
  if data_list:
63
63
  data = data_list
64
64
  else:
65
- remark = f"{platform_user_id}/{user_password}"
66
- pName = "," + ",".join(passengers) + ","
67
- pids = ",".join(pids)
68
- data = [
69
- {"tradingDat": datetime.now().strftime("%Y-%m-%d %H:%M"), "outTktPF": "G航司官网", "outTktLoginCode": "",
70
- "typeName": "VCC", "accountID": "8", "accountName": "VCC", "transactionAmount": f"{amount}",
71
- "mainCheckNumber": "", "airCoOrderID": f"{pre_order_id}", "QuotaResultAmount": "0.00",
72
- "remark": f"{quote(remark)}", "flightIdx": ",1,", "pName": f"{pName}", "orderID": f"{order_id}",
73
- "businessTypeName": "机票", "tradingItems": "机票支出", "actualAmount": 0, "pType": "成人",
74
- "fids": f"{fids}", "pids": f"{pids}", "iscandel": "true", "isbatch": "false",
75
- "MainCheckNumberValus": f"{transaction_id}",
76
- "OfficeNo": "", "PriceStdActual": "0.00", "ReturnAmount": "0.0000", "OffsetReturnAmount": "0.00",
77
- "profitRemark": "", "preSaleType": "", "ErrorType": "", "OutTktPFTypeID": "34", "OutTicketAccount": "",
78
- "OutTicketAccountID": "", "OutTicketPWD": "", "OutTicketTel": "", "OutTicketPNR": ""}
65
+ pName = "," + ",".join(
66
+ fill_procurement_dto.passenger_names) + "," if fill_procurement_dto.passenger_names else ''
67
+ pids = ",".join(fill_procurement_dto.passenger_ids) if fill_procurement_dto.passenger_ids else ''
68
+ data = [{
69
+ "tradingDat": datetime.now().strftime("%Y-%m-%d %H:%M"),
70
+ "outTktPF": f"{fill_procurement_dto.out_ticket_platform or ''}", "outTktLoginCode": "",
71
+ "typeName": f"{fill_procurement_dto.type_name or ''}",
72
+ "accountID": f"{fill_procurement_dto.purchase_account_id or ''}",
73
+ "accountName": f"{fill_procurement_dto.purchase_account or ''}",
74
+ "transactionAmount": f"{fill_procurement_dto.transaction_amount}",
75
+ "mainCheckNumber": "",
76
+ "airCoOrderID": f"{fill_procurement_dto.air_co_order_id}", "QuotaResultAmount": "0.00",
77
+ "remark": f"{quote(fill_procurement_dto.remark) or ''}",
78
+ "flightIdx": f",{fill_procurement_dto.segment_index or '1'},", "pName": f"{pName}",
79
+ "orderID": f"{fill_procurement_dto.order_no}",
80
+ "businessTypeName": "机票", "tradingItems": "机票支出", "actualAmount": 0,
81
+ "pType": f"{fill_procurement_dto.passenger_type}",
82
+ "fids": f"{fill_procurement_dto.flight_ids or ''}",
83
+ "pids": f"{pids or ''}",
84
+ "iscandel": "true", "isbatch": "false",
85
+ "MainCheckNumberValus": f"{fill_procurement_dto.pay_transaction}",
86
+ "OfficeNo": "", "PriceStdActual": "0.00", "ReturnAmount": "0.0000", "OffsetReturnAmount": "0.00",
87
+ "profitRemark": "", "preSaleType": "", "ErrorType": "",
88
+ "OutTktPFTypeID": f"{fill_procurement_dto.out_ticket_platform_type_id or ''}",
89
+ "OutTicketAccount": f"{fill_procurement_dto.out_ticket_account or ''}",
90
+ "OutTicketAccountID": f"{fill_procurement_dto.out_ticket_account_id or ''}",
91
+ "OutTicketPWD": f"{fill_procurement_dto.out_ticket_account_password or ''}",
92
+ "OutTicketTel": f"{fill_procurement_dto.out_ticket_mobile or ''}",
93
+ "OutTicketPNR": ""}
79
94
  ]
80
95
  data = f"list={json.dumps(data)}&isPayAll=true&delTransactionids=&OutTicketLossType&OutTicketLossRemark="
81
96
  return await client.request(
@@ -84,6 +99,150 @@ async def fill_procurement_info_with_http(
84
99
  )
85
100
 
86
101
 
102
+ async def fill_remark_with_http(
103
+ *, remark: str, order_id: int, qlv_protocol: str, qlv_domain: str, retry: int = 1, timeout: int = 5,
104
+ enable_log: bool = True, cookie_jar: Optional[aiohttp.CookieJar] = None, playwright_state: Dict[str, Any] = None
105
+ ) -> Dict[str, Any]:
106
+ client = HttpClientFactory(
107
+ protocol=qlv_protocol, domain=qlv_domain, timeout=timeout, retry=retry,
108
+ enable_log=enable_log, cookie_jar=cookie_jar or aiohttp.CookieJar(), playwright_state=playwright_state
109
+ )
110
+
111
+ headers = {
112
+ "Content-Type": "application/x-www-form-urlencoded; charset=UTF-8"
113
+ }
114
+ data = f"orderid={order_id}&remark={remark}"
115
+ return await client.request(
116
+ method="POST", url="/JPOrderCommon/SaveFreeRemark", headers=headers, is_end=True, data=data.encode("utf-8")
117
+ )
118
+
119
+
120
+ async def update_policy_with_http(
121
+ *, policy_name: str, order_id: int, qlv_protocol: str, qlv_domain: str, retry: int = 1, timeout: int = 5,
122
+ enable_log: bool = True, cookie_jar: Optional[aiohttp.CookieJar] = None, old_policy_name: Optional[str] = None,
123
+ playwright_state: Dict[str, Any] = None,
124
+ ) -> Dict[str, Any]:
125
+ client = HttpClientFactory(
126
+ protocol=qlv_protocol, domain=qlv_domain, timeout=timeout, retry=retry,
127
+ enable_log=enable_log, cookie_jar=cookie_jar or aiohttp.CookieJar(), playwright_state=playwright_state
128
+ )
129
+
130
+ headers = {
131
+ "Content-Type": "application/x-www-form-urlencoded; charset=UTF-8"
132
+ }
133
+ data = f"OrderID={order_id}&PolicyName={policy_name}&OldPolicyName="
134
+ if old_policy_name is not None:
135
+ data = f"{data}{old_policy_name}"
136
+ return await client.request(
137
+ method="POST", url="/OrderProcessing/EditPolicyName", headers=headers, is_end=True, data=data.encode("utf-8")
138
+ )
139
+
140
+
141
+ async def locked_order_with_http(
142
+ *, order_id: int, qlv_protocol: str, qlv_domain: str, retry: int = 1, timeout: int = 5,
143
+ enable_log: bool = True, cookie_jar: Optional[aiohttp.CookieJar] = None, playwright_state: Dict[str, Any] = None
144
+ ) -> Dict[str, Any]:
145
+ client = HttpClientFactory(
146
+ protocol=qlv_protocol, domain=qlv_domain, timeout=timeout, retry=retry,
147
+ enable_log=enable_log, cookie_jar=cookie_jar or aiohttp.CookieJar(), playwright_state=playwright_state
148
+ )
149
+
150
+ result = await client.request(method="GET", url=f"/OrderProcessing/Ticket/{order_id}", is_end=True)
151
+ if "操作提示" in result.get("message", ""):
152
+ soup = BeautifulSoup(result.get("data"), 'html.parser')
153
+ h3_tag = soup.find('h3')
154
+ if h3_tag:
155
+ text = h3_tag.get_text(strip=True)
156
+ if text:
157
+ result["data"] = text
158
+ return result
159
+
160
+
161
+ async def unlock_order_with_http(
162
+ *, order_id: int, qlv_protocol: str, qlv_domain: str, retry: int = 1, timeout: int = 5,
163
+ enable_log: bool = True, cookie_jar: Optional[aiohttp.CookieJar] = None, playwright_state: Dict[str, Any] = None
164
+ ) -> Dict[str, Any]:
165
+ client = HttpClientFactory(
166
+ protocol=qlv_protocol, domain=qlv_domain, timeout=timeout, retry=retry,
167
+ enable_log=enable_log, cookie_jar=cookie_jar or aiohttp.CookieJar(), playwright_state=playwright_state
168
+ )
169
+
170
+ return await client.request(
171
+ method="GET", url=f"/OrderProcessing/UnLock/{order_id}?s={random()}", is_end=True
172
+ )
173
+
174
+
175
+ async def forced_unlock_order_with_http(
176
+ *, order_id: int, qlv_protocol: str, qlv_domain: str, retry: int = 1, timeout: int = 5,
177
+ enable_log: bool = True, cookie_jar: Optional[aiohttp.CookieJar] = None, playwright_state: Dict[str, Any] = None
178
+ ) -> Dict[str, Any]:
179
+ client = HttpClientFactory(
180
+ protocol=qlv_protocol, domain=qlv_domain, timeout=timeout, retry=retry,
181
+ enable_log=enable_log, cookie_jar=cookie_jar or aiohttp.CookieJar(), playwright_state=playwright_state
182
+ )
183
+
184
+ return await client.request(
185
+ method="GET", url=f"/OrderProcessing/UnLockForce/{order_id}?s={random()}", is_end=True
186
+ )
187
+
188
+
189
+ async def fill_itinerary_with_http(
190
+ *, order_id: int, qlv_domain: str, pid: str, tid: str, transaction_id: str, itinerary_id: str, retry: int = 1,
191
+ qlv_protocol: str = "http", timeout: int = 5, enable_log: bool = True, data: Optional[str] = None,
192
+ cookie_jar: Optional[aiohttp.CookieJar] = None, playwright_state: Dict[str, Any] = None
193
+ ) -> Dict[str, Any]:
194
+ client = HttpClientFactory(
195
+ protocol=qlv_protocol, domain=qlv_domain, timeout=timeout, enable_log=enable_log, retry=retry,
196
+ cookie_jar=cookie_jar or aiohttp.CookieJar(), playwright_state=playwright_state
197
+ )
198
+
199
+ headers = {
200
+ "Content-Type": "application/x-www-form-urlencoded; charset=UTF-8",
201
+ "Referer": f"{qlv_protocol}://{qlv_domain}/OrderProcessing/NewTicket_show/{order_id}?&r={datetime.now().strftime("%Y%m%d%H%M%S")}",
202
+ }
203
+ if not data:
204
+ data = f"OrderID={order_id}&OrderPID={pid}&OrderTID={tid}&TicketNo={itinerary_id}&ZJTransactionID={transaction_id}"
205
+ return await client.request(
206
+ method="POST", url="/OrderProcessing/TicketNoSave",
207
+ headers=headers, is_end=True, data=data.encode("utf-8")
208
+ )
209
+
210
+
211
+ async def kick_out_activity_order_with_http(
212
+ *, order_id: int, qlv_protocol: str, qlv_domain: str, retry: int = 1, timeout: int = 5,
213
+ enable_log: bool = True, cookie_jar: Optional[aiohttp.CookieJar] = None, playwright_state: Dict[str, Any] = None
214
+ ) -> Dict[str, Any]:
215
+ client = HttpClientFactory(
216
+ protocol=qlv_protocol, domain=qlv_domain, timeout=timeout, retry=retry,
217
+ enable_log=enable_log, cookie_jar=cookie_jar or aiohttp.CookieJar(), playwright_state=playwright_state
218
+ )
219
+ headers = {
220
+ "Content-Type": "application/x-www-form-urlencoded; charset=UTF-8"
221
+ }
222
+ data = f"ID={order_id}"
223
+ return await client.request(
224
+ method="POST", url=f"/OrderList/KickOutActivityOrders", is_end=True, data=data.encode("utf-8"), headers=headers
225
+ )
226
+
227
+
228
+ async def kick_out_activity_orders_with_http(
229
+ *, order_ids: List[int], qlv_protocol: str, qlv_domain: str, retry: int = 1, timeout: int = 5,
230
+ enable_log: bool = True, cookie_jar: Optional[aiohttp.CookieJar] = None, playwright_state: Dict[str, Any] = None
231
+ ) -> Dict[str, Any]:
232
+ client = HttpClientFactory(
233
+ protocol=qlv_protocol, domain=qlv_domain, timeout=timeout, retry=retry,
234
+ enable_log=enable_log, cookie_jar=cookie_jar or aiohttp.CookieJar(), playwright_state=playwright_state
235
+ )
236
+ headers = {
237
+ "Content-Type": "application/x-www-form-urlencoded; charset=UTF-8"
238
+ }
239
+ order_ids_str = ",".join(str(id) for id in order_ids) + ","
240
+ data = f"OrderIDS={order_ids_str}"
241
+ return await client.request(
242
+ method="POST", url=f"/OrderList/YJTCActivityOrders", is_end=True, data=data.encode("utf-8"), headers=headers
243
+ )
244
+
245
+
87
246
  def order_info_static_headers() -> OrderedDict[str, str]:
88
247
  return OrderedDict([
89
248
  ("receipted_ota", "OTA实收"), # 0
@@ -199,6 +358,7 @@ def flight_extend_headers() -> OrderedDict[str, str]:
199
358
  ("code_arr", " 抵达机场"), # 11
200
359
  ("pid", "乘客ID"), # 12
201
360
  ("fid", "航段ID"), # 13
361
+ ("tid", "乘客表ID"), # 14
202
362
  ])
203
363
 
204
364
 
@@ -350,7 +510,10 @@ def parse_order_flight_table_row(
350
510
  ) -> Dict[str, Any]:
351
511
  """解析航班表每一行的数据"""
352
512
  tds = tr.find_all("td", recursive=False)
353
- values = {get_key_by_index(index=12, ordered_dict=extend_headers): tr["pid"]}
513
+ values = {
514
+ get_key_by_index(index=12, ordered_dict=extend_headers): tr["pid"],
515
+ get_key_by_index(index=14, ordered_dict=extend_headers): tr["tid"]
516
+ }
354
517
  for idx, td in enumerate(tds):
355
518
  if idx >= len(headers):
356
519
  continue
@@ -23,6 +23,96 @@ class DomesticActivityOrderPage(BasePo):
23
23
  super().__init__(page, url)
24
24
  self.__page = page
25
25
 
26
+ async def get_flight_table_locator(self, timeout: float = 5.0) -> Locator:
27
+ """
28
+ 获取table
29
+ :param timeout:
30
+ :return:
31
+ """
32
+ selecor: str = 'xpath=//table[@class="table table_hover table_border table_center"]//tbody'
33
+ return await self.get_locator(selector=selecor, timeout=timeout)
34
+
35
+ async def get_flight_table_trs_locator(self, timeout: float = 5.0) -> Locator:
36
+ """
37
+ 获取table所有tr locator对象
38
+ :param timeout:
39
+ :return:
40
+ """
41
+ selecor: str = 'xpath=//table[@class="table table_hover table_border table_center"]/tbody/tr'
42
+ return await self.get_locator(selector=selecor, timeout=timeout)
43
+
44
+ async def get_flight_table_tds_th(self, locator: Locator, timeout: float = 5.0) -> Locator:
45
+ """
46
+ 获取table所有tr下的th locator对象
47
+ :param locator:
48
+ :param timeout:
49
+ :return:
50
+ """
51
+ selecor: str = 'xpath=./th'
52
+ return await self.get_sub_locator(locator=locator, selector=selecor, timeout=timeout)
53
+
54
+ async def get_flight_table_trs_td(self, locator: Locator, timeout: float = 5.0) -> Locator:
55
+ """
56
+ 获取table所有tr下的td locator对象
57
+ :param locator:
58
+ :param timeout:
59
+ :return:
60
+ """
61
+ selecor: str = 'xpath=./td'
62
+ return await self.get_sub_locator(locator=locator, selector=selecor, timeout=timeout)
63
+
64
+ async def get_flight_table_td_order_id(self, locator: Locator, timeout: float = 5.0) -> str:
65
+ """
66
+ 获取table 行中的订单id
67
+ :param locator:
68
+ :param timeout:
69
+ :return:
70
+ """
71
+ selecor: str = 'xpath=./a'
72
+ locator = await self.get_sub_locator(locator=locator, selector=selecor, timeout=timeout)
73
+ return (await locator.inner_text()).strip()
74
+
75
+ async def get_flight_table_td_urgant(self, locator: Locator, timeout: float = 5.0) -> str:
76
+ """
77
+ 获取table 行中的紧急状态
78
+ :param locator:
79
+ :param timeout:
80
+ :return:
81
+ """
82
+ selecor: str = 'xpath=./font'
83
+ locator = await self.get_sub_locator(locator=locator, selector=selecor, timeout=timeout)
84
+ return (await locator.inner_text()).strip()
85
+
86
+ async def get_flight_table_td_operation_lock_btn(self, locator: Locator, timeout: float = 5.0) -> Locator:
87
+ """
88
+ 获取table 行中的锁单按钮
89
+ :param locator:
90
+ :param timeout:
91
+ :return:
92
+ """
93
+ selecor: str = 'xpath=./a'
94
+ return await self.get_sub_locator(locator=locator, selector=selecor, timeout=timeout)
95
+
96
+ async def get_flight_table_td_operation_pop_btn(self, locator: Locator, timeout: float = 5.0) -> Locator:
97
+ """
98
+ 获取table 行中的剔出按钮
99
+ :param locator:
100
+ :param timeout:
101
+ :return:
102
+ """
103
+ selecor: str = 'xpath=./button'
104
+ return await self.get_sub_locator(locator=locator, selector=selecor, timeout=timeout)
105
+
106
+ async def get_flight_table_td_operation_substitute_btn(self, locator: Locator, timeout: float = 5.0) -> Locator:
107
+ """
108
+ 获取table 行中的补位按钮
109
+ :param locator:
110
+ :param timeout:
111
+ :return:
112
+ """
113
+ selecor: str = 'xpath=./a'
114
+ return await self.get_sub_locator(locator=locator, selector=selecor, timeout=timeout)
115
+
26
116
  async def get_flight_table(self, timeout: float = 5.0) -> Tuple[bool, Union[Locator, str]]:
27
117
  try:
28
118
  locator = self.__page.locator(self.__table_selector)
@@ -84,6 +174,7 @@ class DomesticActivityOrderPage(BasePo):
84
174
  3. 主流程:分页 + 每页解析 tbody
85
175
  ------------------------------------------------------------
86
176
  """
177
+
87
178
  async def parse_table_with_pagination(self, refresh_wait_time: float = 10.0) -> List[Dict[str, Any]]:
88
179
  """
89
180
  refresh_wait_time: 翻页后等待时间
@@ -11,8 +11,7 @@
11
11
  """
12
12
  from typing import Tuple, Union
13
13
  from playwright_helper.libs.base_po import BasePo
14
- from qlv_helper.utils.ocr_helper import get_image_text
15
- from playwright.async_api import Page, TimeoutError as PlaywrightTimeoutError, Locator
14
+ from playwright.async_api import Page, TimeoutError as PlaywrightTimeoutError, Locator, ElementHandle
16
15
 
17
16
 
18
17
  class LoginPage(BasePo):
@@ -22,104 +21,67 @@ class LoginPage(BasePo):
22
21
  super().__init__(page, url)
23
22
  self.__page = page
24
23
 
25
- async def get_login_username_input(self, timeout: float = 5.0) -> Tuple[bool, Union[Locator, str]]:
24
+ async def get_login_username_input(self, timeout: float = 5.0) -> Locator:
26
25
  """
27
26
  获取登录页面的用户名输入框
28
27
  :param timeout: 超时时间(秒)
29
28
  :return: (是否存在, 错误信息|元素对象)
30
- :return:
31
29
  """
32
30
  selector: str = '//input[@id="UserName"]'
33
- try:
34
- locator = self.__page.locator(selector)
35
- if locator:
36
- await locator.wait_for(state='visible', timeout=timeout * 1000)
37
- return True, locator
38
- else:
39
- return False, '没有找到登录页面中的【用户名】输入框'
40
- except PlaywrightTimeoutError:
41
- return False, f"元素 '{selector}' 未在 {timeout} 秒内找到"
42
- except Exception as e:
43
- return False, f"检查元素时发生错误: {str(e)}"
31
+ return await self.get_locator(selector=selector, timeout=timeout)
44
32
 
45
- async def get_login_password_input(self, timeout: float = 5.0) -> Tuple[bool, Union[Locator, str]]:
33
+ async def get_login_password_input(self, timeout: float = 5.0) -> Locator:
46
34
  """
47
35
  获取登录页面的密码输入框
48
36
  :param timeout: 超时时间(秒)
49
37
  :return: (是否存在, 错误信息|元素对象)
50
- :return:
51
38
  """
52
39
  selector: str = '//input[@id="Password"]'
53
- try:
54
- locator = self.__page.locator(selector)
55
- if locator:
56
- await locator.wait_for(state='visible', timeout=timeout * 1000)
57
- return True, locator
58
- else:
59
- return False, '没有找到登录页面中的【密码】输入框'
60
- except PlaywrightTimeoutError:
61
- return False, f"元素 '{selector}' 未在 {timeout} 秒内找到"
62
- except Exception as e:
63
- return False, f"检查元素时发生错误: {str(e)}"
40
+ return await self.get_locator(selector=selector, timeout=timeout)
64
41
 
65
- async def get_login_number_code_input(self, timeout: float = 5.0) -> Tuple[bool, Union[Locator, str]]:
42
+ async def get_captcha(self, timeout: float = 5.0) -> Locator:
66
43
  """
67
- 获取登录页面的数字验证码输入框,第一层验证码
44
+ 获取验证码Locator对象
68
45
  :param timeout: 超时时间(秒)
69
46
  :return: (是否存在, 错误信息|元素对象)
70
- :return:
71
47
  """
72
- selector: str = '//input[@id="Code"]'
73
- try:
74
- locator = self.__page.locator(selector)
75
- if locator:
76
- await locator.wait_for(state='visible', timeout=timeout * 1000)
77
- return True, locator
78
- else:
79
- return False, '没有找到登录页面中的【数字验证码】输入框'
80
- except PlaywrightTimeoutError:
81
- return False, f"元素 '{selector}' 未在 {timeout} 秒内找到"
82
- except Exception as e:
83
- return False, f"检查元素时发生错误: {str(e)}"
48
+ selector: str = 'xpath=//div[@class="form_row"]/img[@onclick="this.src=this.src+\'?\'"]'
49
+ return await self.get_locator(selector=selector, timeout=timeout)
84
50
 
85
- async def get_login_btn(self, timeout: float = 5.0) -> Tuple[bool, Union[Locator, str]]:
51
+ @staticmethod
52
+ async def get_captcha_type(locator: Locator, timeout: float = 5.0) -> int:
86
53
  """
87
- 获取登录页面的登录按钮
54
+ 获取验证码的类型
55
+ :param locator: 验证码Locator对象
88
56
  :param timeout: 超时时间(秒)
89
57
  :return: (是否存在, 错误信息|元素对象)
90
- :return:
91
58
  """
92
- selector: str = '//input[@class="login-btn"]'
93
- try:
94
- locator = self.__page.locator(selector)
95
- if locator:
96
- await locator.wait_for(state='visible', timeout=timeout * 1000)
97
- return True, locator
98
- else:
99
- return False, '没有找到登录页面中的【登录】按钮'
100
- except PlaywrightTimeoutError:
101
- return False, f"元素 '{selector}' 未在 {timeout} 秒内找到"
102
- except Exception as e:
103
- return False, f"检查元素时发生错误: {str(e)}"
59
+ img_src: str = await locator.get_attribute(name="src", timeout=timeout)
60
+ img_src_slice = img_src.split("=")
61
+ return int(img_src_slice[-1][0])
104
62
 
105
- async def get_number_code(self, timeout: float = 5.0) -> Tuple[bool, str]:
63
+ async def get_captcha_image(self, timeout: float = 5.0) -> ElementHandle:
106
64
  selector: str = '//div[@id="signup_forms"]//img'
107
- return await get_image_text(page=self.__page, selector=selector, timeout=timeout)
65
+ locator: Locator = await self.get_locator(selector=selector, timeout=timeout)
66
+ return await locator.element_handle(timeout=timeout * 1000)
108
67
 
109
- async def is_exist_login_warn(self, timeout: float = 5.0) -> bool:
110
- selector: str = '//p[@class="login_warn"]'
111
- try:
112
- locator = self.__page.locator(selector)
113
- if locator:
114
- text: str = await locator.text_content(timeout=timeout * 1000)
115
- if text.strip() != "":
116
- return True
117
- else:
118
- return False
119
- else:
120
- return False
121
- except (PlaywrightTimeoutError, Exception):
122
- return False
68
+ async def get_login_captcha_input(self, timeout: float = 5.0) -> Locator:
69
+ """
70
+ 获取登录页面的验证码输入框
71
+ :param timeout: 超时时间(秒)
72
+ :return: (是否存在, 错误信息|元素对象)
73
+ """
74
+ selector: str = '//input[@id="Code"]'
75
+ return await self.get_locator(selector=selector, timeout=timeout)
76
+
77
+ async def get_login_btn(self, timeout: float = 5.0) -> Locator:
78
+ """
79
+ 获取登录页面的登录按钮
80
+ :param timeout: 超时时间(秒)
81
+ :return: (是否存在, 错误信息|元素对象)
82
+ """
83
+ selector: str = '//input[@class="login-btn"]'
84
+ return await self.get_locator(selector=selector, timeout=timeout)
123
85
 
124
86
  async def get_wechat_entrance(self, timeout: float = 5.0) -> Tuple[bool, Union[Locator, str]]:
125
87
  selector: str = '//img[@src="/images/weixin.png"]'
@@ -9,9 +9,8 @@
9
9
  # Copyright ©2011-2025. Hunan xxxxxxx Company limited. All rights reserved.
10
10
  # ---------------------------------------------------------------------------------------------------------
11
11
  """
12
- from typing import Tuple, Union
12
+ from playwright.async_api import Page, Locator
13
13
  from playwright_helper.libs.base_po import BasePo
14
- from playwright.async_api import Page, TimeoutError as PlaywrightTimeoutError, Locator
15
14
 
16
15
 
17
16
  class MainPage(BasePo):
@@ -23,48 +22,18 @@ class MainPage(BasePo):
23
22
  self.url = url
24
23
  self.__page = page
25
24
 
26
- async def get_confirm_btn_with_system_notice_dialog(self, timeout: float = 5.0) -> Tuple[bool, Union[Locator, str]]:
25
+ async def get_confirm_btn_with_system_notice_dialog(self, timeout: float = 5.0) -> Locator:
27
26
  """
28
27
  获取系统通知弹框中的确认按钮,注意这个地方,存在多个叠加的弹框,因此用last()方法,只需定位到最上面的那个弹框就行
29
28
  :return:
30
29
  """
31
30
  selector: str = "//div[@class='CommonAlert'][last()]//a[@class='CommonAlertBtnConfirm']"
32
- try:
33
- locator = self.__page.locator(selector)
34
- if locator:
35
- await locator.wait_for(state='visible', timeout=timeout * 1000)
36
- return True, locator
37
- else:
38
- return False, '没有找到首页中的【系统提醒-确定】按钮'
39
- except PlaywrightTimeoutError:
40
- return False, f"元素 '{selector}' 未在 {timeout} 秒内找到"
41
- except Exception as e:
42
- return False, f"检查元素时发生错误: {str(e)}"
31
+ return await self.get_locator(selector=selector, timeout=timeout)
43
32
 
44
- async def get_level1_menu_order_checkout(self, timeout: float = 5.0) -> Tuple[bool, Union[Locator, str]]:
33
+ async def get_level1_menu_order_checkout(self, timeout: float = 5.0) -> Locator:
45
34
  selector: str = "//span[contains(normalize-space(), '订单出票')]"
46
- try:
47
- locator = self.__page.locator(selector)
48
- if locator:
49
- await locator.wait_for(state='visible', timeout=timeout * 1000)
50
- return True, locator
51
- else:
52
- return False, '没有找到首页中的【订单出票】左侧一级导航菜单'
53
- except PlaywrightTimeoutError:
54
- return False, f"元素 '{selector}' 未在 {timeout} 秒内找到"
55
- except Exception as e:
56
- return False, f"检查元素时发生错误: {str(e)}"
35
+ return await self.get_locator(selector=selector, timeout=timeout)
57
36
 
58
- async def get_level2_menu_order_checkout(self, timeout: float = 5.0) -> Tuple[bool, Union[Locator, str]]:
37
+ async def get_level2_menu_order_checkout(self, timeout: float = 5.0) -> Locator:
59
38
  selector: str = "//a[@menuname='国内活动订单']"
60
- try:
61
- locator = self.__page.locator(selector)
62
- if locator:
63
- await locator.wait_for(state='visible', timeout=timeout * 1000)
64
- return True, locator
65
- else:
66
- return False, '没有找到首页中的【国内活动订单】左侧二级导航菜单'
67
- except PlaywrightTimeoutError:
68
- return False, f"元素 '{selector}' 未在 {timeout} 秒内找到"
69
- except Exception as e:
70
- return False, f"检查元素时发生错误: {str(e)}"
39
+ return await self.get_locator(selector=selector, timeout=timeout)
@@ -263,7 +263,7 @@ class OrderDetailPage(BasePo):
263
263
  :return: (是否存在, 错误信息|元素对象)
264
264
  """
265
265
  selector: str = '//a[@id="alertMsg_btnConfirm"]/cite'
266
- return await self.get_locator(selector=selector, timeout=timeout)
266
+ return await self.get_locator(selector=selector, timeout=timeout, strict=False)
267
267
 
268
268
  async def get_policy_input(self, timeout: float = 5.0) -> Locator:
269
269
  """
@@ -273,3 +273,48 @@ class OrderDetailPage(BasePo):
273
273
  """
274
274
  selector: str = '//legend//input[@name="PolicyName"]'
275
275
  return await self.get_locator(selector=selector, timeout=timeout)
276
+
277
+ async def get_purchase_info_transaction_id(self, timeout: float = 5.0) -> List[str]:
278
+ """
279
+ 获取订单详情页面的采购信息流水
280
+ :param timeout:
281
+ :return:
282
+ """
283
+ # selector: str = '//tr[@class="PurchaseInfoClass"]'
284
+ selector: str = '//table[@id="PurchaseInfos"]/tbody/tr'
285
+ loc: Locator = await self.get_locator(selector=selector, timeout=timeout)
286
+ locators = await loc.all()
287
+ transaction_ids = list()
288
+ for locator in locators:
289
+ transaction_id = await locator.get_attribute("transactionid")
290
+ if transaction_id:
291
+ transaction_ids.append(transaction_id.strip())
292
+ return transaction_ids
293
+
294
+ async def get_relation_order_detail_link(self, timeout: float = 5.0) -> Locator:
295
+ """
296
+ 获取关联订单的详情链接
297
+ :param timeout: 超时时间
298
+ :return:
299
+ """
300
+ selector: str = 'xpath=//span[contains(text(), "关联订单")]/../a'
301
+ return await self.get_locator(selector=selector, timeout=timeout)
302
+
303
+ async def get_message_alert(self, timeout: float = 5.0) -> Locator:
304
+ """
305
+ 获取详情页面消息弹框
306
+ :param timeout: 超时时长
307
+ :return:
308
+ """
309
+ selector: str = 'xpath=//a[@id="alertMsg_btnConfirm"]/cite/../..'
310
+ return await self.get_locator(selector=selector, timeout=timeout)
311
+
312
+ async def get_message_content(self, timeout: float = 5.0) -> str:
313
+ """
314
+ 获取详情页面消息弹框
315
+ :param timeout: 超时时长
316
+ :return:
317
+ """
318
+ selector: str = 'xpath=//a[@id="alertMsg_btnConfirm"]/cite/../../p'
319
+ locator = await self.get_locator(selector=selector, timeout=timeout)
320
+ return (await locator.inner_text()).strip()