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.
- {python_qlv_helper-0.6.0.dist-info → python_qlv_helper-0.9.3.dist-info}/METADATA +9 -7
- {python_qlv_helper-0.6.0.dist-info → python_qlv_helper-0.9.3.dist-info}/RECORD +17 -16
- qlv_helper/config/url_const.py +2 -0
- qlv_helper/controller/main_page.py +18 -2
- qlv_helper/controller/order_detail.py +202 -212
- qlv_helper/controller/order_table.py +178 -16
- qlv_helper/controller/user_login.py +106 -102
- qlv_helper/controller/wechat_login.py +50 -0
- qlv_helper/http/order_page.py +187 -24
- qlv_helper/po/domestic_activity_order_page.py +91 -0
- qlv_helper/po/login_page.py +36 -74
- qlv_helper/po/main_page.py +7 -38
- qlv_helper/po/order_detail_page.py +46 -1
- qlv_helper/utils/ocr_helper.py +38 -43
- {python_qlv_helper-0.6.0.dist-info → python_qlv_helper-0.9.3.dist-info}/WHEEL +0 -0
- {python_qlv_helper-0.6.0.dist-info → python_qlv_helper-0.9.3.dist-info}/licenses/LICENSE +0 -0
- {python_qlv_helper-0.6.0.dist-info → python_qlv_helper-0.9.3.dist-info}/top_level.txt +0 -0
qlv_helper/http/order_page.py
CHANGED
|
@@ -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
|
|
47
|
-
*,
|
|
48
|
-
|
|
49
|
-
|
|
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=
|
|
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"{
|
|
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
|
-
|
|
66
|
-
|
|
67
|
-
pids = ",".join(
|
|
68
|
-
data = [
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
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 = {
|
|
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: 翻页后等待时间
|
qlv_helper/po/login_page.py
CHANGED
|
@@ -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
|
|
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) ->
|
|
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
|
-
|
|
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) ->
|
|
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
|
-
|
|
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
|
|
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 = '
|
|
73
|
-
|
|
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
|
-
|
|
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
|
-
|
|
93
|
-
|
|
94
|
-
|
|
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
|
|
63
|
+
async def get_captcha_image(self, timeout: float = 5.0) -> ElementHandle:
|
|
106
64
|
selector: str = '//div[@id="signup_forms"]//img'
|
|
107
|
-
|
|
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
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
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"]'
|
qlv_helper/po/main_page.py
CHANGED
|
@@ -9,9 +9,8 @@
|
|
|
9
9
|
# Copyright ©2011-2025. Hunan xxxxxxx Company limited. All rights reserved.
|
|
10
10
|
# ---------------------------------------------------------------------------------------------------------
|
|
11
11
|
"""
|
|
12
|
-
from
|
|
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) ->
|
|
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
|
-
|
|
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) ->
|
|
33
|
+
async def get_level1_menu_order_checkout(self, timeout: float = 5.0) -> Locator:
|
|
45
34
|
selector: str = "//span[contains(normalize-space(), '订单出票')]"
|
|
46
|
-
|
|
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) ->
|
|
37
|
+
async def get_level2_menu_order_checkout(self, timeout: float = 5.0) -> Locator:
|
|
59
38
|
selector: str = "//a[@menuname='国内活动订单']"
|
|
60
|
-
|
|
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()
|