From cc59e8b8da169448826643aabe624d3089dc6dcb Mon Sep 17 00:00:00 2001 From: YiLin <482244139@qq.com> Date: Mon, 2 Feb 2026 11:47:53 +0800 Subject: [PATCH] first commit --- __pycache__/permissions.cpython-312.pyc | Bin 0 -> 11844 bytes appium_automation.log | 0 globals/__pycache__/alarm.cpython-312.pyc | Bin 0 -> 3665 bytes globals/__pycache__/apis.cpython-312.pyc | Bin 0 -> 19714 bytes .../__pycache__/driver_utils.cpython-312.pyc | Bin 0 -> 31423 bytes globals/__pycache__/ex_apis.cpython-312.pyc | Bin 0 -> 12072 bytes .../global_variable.cpython-312.pyc | Bin 0 -> 611 bytes globals/__pycache__/ids.cpython-312.pyc | Bin 0 -> 2616 bytes globals/alarm.py | 88 + globals/apis.py | 479 +++++ globals/driver_utils.py | 763 +++++++ globals/ex_apis.py | 298 +++ globals/global_variable.py | 17 + globals/ids.py | 59 + main.py | 478 +++++ music/901135.wav | Bin 0 -> 209452 bytes page_objects/__init__.py | 0 .../__pycache__/__init__.cpython-312.pyc | Bin 0 -> 145 bytes .../download_tabbar_page.cpython-312.pyc | Bin 0 -> 16553 bytes .../__pycache__/login_page.cpython-312.pyc | Bin 0 -> 4007 bytes .../measure_tabbar_page.cpython-312.pyc | Bin 0 -> 18265 bytes .../more_download_page.cpython-312.pyc | Bin 0 -> 16769 bytes .../screenshot_page.cpython-312.pyc | Bin 0 -> 68095 bytes ...ection_mileage_config_page.cpython-312.pyc | Bin 0 -> 44726 bytes .../upload_config_page.cpython-312.pyc | Bin 0 -> 77778 bytes page_objects/call_xie.py | 46 + page_objects/download_tabbar_page.py | 319 +++ page_objects/login_page.py | 78 + page_objects/more_download_page.py | 506 +++++ page_objects/screenshot_page.py | 1471 +++++++++++++ page_objects/upload_config_page.py | 1875 +++++++++++++++++ permissions.py | 251 +++ test_results/上传失败的断点.txt | 5 + test_results/上传成功的断点.txt | 0 上传人员信息.xlsx | Bin 0 -> 24659 bytes 35 files changed, 6733 insertions(+) create mode 100644 __pycache__/permissions.cpython-312.pyc create mode 100644 appium_automation.log create mode 100644 globals/__pycache__/alarm.cpython-312.pyc create mode 100644 globals/__pycache__/apis.cpython-312.pyc create mode 100644 globals/__pycache__/driver_utils.cpython-312.pyc create mode 100644 globals/__pycache__/ex_apis.cpython-312.pyc create mode 100644 globals/__pycache__/global_variable.cpython-312.pyc create mode 100644 globals/__pycache__/ids.cpython-312.pyc create mode 100644 globals/alarm.py create mode 100644 globals/apis.py create mode 100644 globals/driver_utils.py create mode 100644 globals/ex_apis.py create mode 100644 globals/global_variable.py create mode 100644 globals/ids.py create mode 100644 main.py create mode 100644 music/901135.wav create mode 100644 page_objects/__init__.py create mode 100644 page_objects/__pycache__/__init__.cpython-312.pyc create mode 100644 page_objects/__pycache__/download_tabbar_page.cpython-312.pyc create mode 100644 page_objects/__pycache__/login_page.cpython-312.pyc create mode 100644 page_objects/__pycache__/measure_tabbar_page.cpython-312.pyc create mode 100644 page_objects/__pycache__/more_download_page.cpython-312.pyc create mode 100644 page_objects/__pycache__/screenshot_page.cpython-312.pyc create mode 100644 page_objects/__pycache__/section_mileage_config_page.cpython-312.pyc create mode 100644 page_objects/__pycache__/upload_config_page.cpython-312.pyc create mode 100644 page_objects/call_xie.py create mode 100644 page_objects/download_tabbar_page.py create mode 100644 page_objects/login_page.py create mode 100644 page_objects/more_download_page.py create mode 100644 page_objects/screenshot_page.py create mode 100644 page_objects/upload_config_page.py create mode 100644 permissions.py create mode 100644 test_results/上传失败的断点.txt create mode 100644 test_results/上传成功的断点.txt create mode 100644 上传人员信息.xlsx diff --git a/__pycache__/permissions.cpython-312.pyc b/__pycache__/permissions.cpython-312.pyc new file mode 100644 index 0000000000000000000000000000000000000000..23118b8b685afe3114bc00b0615c493eb9bfaa0b GIT binary patch literal 11844 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 literal 0 HcmV?d00001 diff --git a/globals/__pycache__/driver_utils.cpython-312.pyc b/globals/__pycache__/driver_utils.cpython-312.pyc new file mode 100644 index 0000000000000000000000000000000000000000..81dc29df37504cf6cf780adbfa495067cd3d161b GIT binary patch literal 31423 zcmcJ2dt4M(nrKx&pr3RD4K(lOp@=B>exOlNd;w}y)QB2Hx`ukU&K3!_oTz*C;q)p)v#Vg3^X;-e5eH*GA#oMr5coo!}n=$ox! zNmx;%Aa7-jlDt(lD)Ls>sL4B~CI;S$=GYcZjfTa%%4Thgu0}`vs%CwQp~gV`>Skk0 zTumJD$26N-%r$1>k8O@`NvKIAeoeEbC8;K<2YtOXD!#fXhCcr!2 zo@Gyj_Y5e}0`HmjYc$pFtH-_m!FmVD98BKYy3D0zxt3H@gZ#u~JA4Ax&5aTT32$ z416PV8-t_e*sxUbWpMl@v3rlFVTx<%lIU<%+)0+a53L({$!W zg1l_6+8c9Nv6IPT-1%ae`3(2d&RAJHXNq4!Z(*J#x(mfPZ>-pk2snq@6s;VKyXejm zn&yacp*NJO5Nm)r{r#mTxl6>HP4h%w=q;{;@G|Ym?uB9+F~u>4OaFnIOVu)7b<<+; zYv?VeW0^+*hAgue-VZAGGRN6R8GA}+?8C9g+2cY?q1Ceunys_JGuHj`*y~S?o%#{o zk#4;4_Slns6E8kC_ITge+g)QPJ{W(#pCqKIPd^IbQsTEeaPO*Zw3phhpZ{>|wG*4m zNqk*fTWwoi{n5GxN2zV>{pTj0>l-`u_*l==f4X$)#>L+2=U*Egct5wkwWat_Q`7O{ z`lg2ZTq$E+y}R*Pqq|+o*>eHfQzpcQl~63EEv{;9XmpBcXj!Dd>mNNg_R^1OYiH4Y zc#iEjZltBQS<+@VcK*ksA3in5CPm;@OCt-R!m*z|I{xEx5}3wbd;9u3|1{S9!uXRf zjh%RZ?9!uS0|TVCzu;~}Az}02O16z%>O$B?gK#jm%xULZ8|^`ZeCMxj>}q3}V78~R zPC5|EdVkA6J<%s*psi0z0g-`dxH(e zw$d}`FQg+8DQLFw?w+wHU%6p{;{K9}WrhP#?6r^#w^Irg`9zaoGj{$IB=lIZ94V4c zMhfv#nlZFeynS)($4`~o{t}8B^F1-{>n^ngW9UF{Eldx_Hn%pw%xDOz z8l8t*IXpXp#~OTSx*Z%&qo4{0G^lnpI~;96t#lp-)eeqp<$_9=n}am%ZH}O(mNclgHmG%Tb@h%z zu=IjD>|vd|*70C{Fs8Y+zOLC7R6XEED`j(bpr zizUc9TzItNZh_D3lJe61_j0XGF!fyfarxGI+L~MI?E4#RH;mytjy-QO3uw^{dw1ieXH- zLveXs8v_|Lx;FY%sev>I_*E%^G!jk;q!(b=8pxW1@AN=QI=(G|8HM=H4djyV*@482 zu8ogXO~yb)!>Y7FRT`hZa=_tNm0wfo#fXyrWWQ?Jq$*t39LG#mJ&jegB0NTW5L)}7S#%s*uu4X;utCSXeHad&%pP5QT^ddoMAP92A38BZE} zV}07*hxplRKV5kFflroPp2a6u`ZZPJ*SOc*eP#c6e7IogV8K#;<}yBcxnENn`r1BR zuwt-a#ZbXL{LEE+@@l_k%{6`EbH|>2a8RH9>V`>$(wG!5C!f`y(LZMd;A!K&om8_J zX9;MGx4YHfO(sEOZo3x1VE$UOHm`gR^XVMz#&qSUi`T|(OjiE(9`nXH#b-tq{GY`s zHYTV)GsmDmSq+Km1vCpXGYrl$f?!?&_k{W^<7Kt(qZyTZ!?vZ;Jo z>16GyPKB3orG}8rCJAYjiekJ9NUxS3%QQ&{WT=d3)u9Qm z14=T4N)UaW%4xJ#>s11pAVa6yb$~444oDtAmeS=Y_LRNW%UiUUX^(N6Wn~-#G&WHT zdnGirDM^eAy+xGcNwqsgjO|t*W`V@g*p20k`29@*MdEU&(aX4#YCCH;r2+9E@9SP5 zC^}UStDvm zP|uO*aK8h#2>{!~|0ZlLcY(`vV4HN8G9=ha$Y!$VBj_YSk;ebAYy6de8vEecv6D}a zUV8ERg_p;BFO2p4bo|`O@#niq65@=>(M&@AmWHqkikgvh(n%IVLH~5Ad+hXu8(k;= zbm?TMKshFOsI|4Z)FxCn* zbp&*F;*Iw=1a&S-Qv*uuL7Gdzr7yid)_;cf5b$bQ8l*ymW)QgC=z8(`M<=ie6hH${ zjt)FE{-YD6wrY<_N;~%6F90I~aMcUT3ck>76KRHdVRzU)h}zNtq8KklK1*#k?C`;Zq@jfJQaWJtgC}6}UH^v< zMhBjTnN9Q3(E|Jxi<|i1%*4fW;l+gwB*KIYaywuY?AU}BV|$nAqfTQT(?VWPmwn@naj@qDax#F95`Su|)_)UExhXc0xT zecK1)3kGt#D*i(sFQSBLy#_vi37@>wuUQr_SteD*YU`*ex#w7~*KeBBzhQXM`m2l9 zj~Eh9Z#lW8x2Cs>pS6)s+vGQF4kTIuK}^pXPMbHFHm}6Y&!-*m8}7#zBEoIG?b-x` z{QkVP3CzbuGr|3JLe9n<<*yfO(7mTjS6;0A)R2j8aSBBJMxD2QuJSi?4d`FKa$|<_ zx22ijewLwzn0j2V;)AoCpyAKJ>4B$+>uyLmr0G<7RouJ~s^nFo|YOx$3TqG{IIQFg*S&7lcgtj%D*NroAX_cQHR{jdHNUzRrlSJ==rQ@%?`t4FLynhUe!x!s8SkVoDQ7IvOft5uAe+XopG`{5$TS2gx1UV$3j{CakBcQ-4|wy#v23sDKMvw!s_Qry|Lo{+5t?9PLr(8 zsaM`?uL&Sl6oMG+#^1k>n#9(?%$g;uW9oj*k&h^x;s)8Mf}~iST`0XGFpYaSu19&Q z1fUAO5L63GJTm|#&><3h8c<#kRqjRV#IK?>A-ZrY)IK@4B2sPzT@Gu@4^K($ z3dyZVQ5_nAroM;6lObr31P(ytas?3ODF+9Msec9`Rlmecq2L)m>jNeOawFdR#puOn zLhu+J`1$zhp0U$E5~Y4XmEj`948MH-`ZMprC(7;+6X1w^@Da)ZsZjBB?~N0uBZ;w6 z8)30{kh~HkXYvsi$F`e}Ee}7Vk}w`TkjLM8b*%s8 z>ldDv7v=$ElCjZABgwtS&;9KBM?KeHJ%xQHS?KKHkoUqNkF>hc3>aomhXNlek3*)< z(Z;r2PT+qW4JM6*c^G`;pFBk*`+L8jQa`#lVQ$fSVCFzoVd--)J)JXQ zm45W<_#+oY$=_BFLMLp&)ABttFdmk~Socd~7vG1%MW%o_cl)1!afi|OAPitG$Y+6A zya%^=+gSe-AQ2_NnRxw&V?EE%jPeYgTvFZ1E|}i(cwx4@e1eupn15ldrSWtv(Zn8{ zv(TQxd~Q4VxE zz{w%98&rb`vcVC|a5!Ba&LPU%C>N}j^EgS$*;ovY5)p@T$fx9N=-^0DSzsRe^3g%? zax>Av?SjKqLu8d4DqB!NrNk%^w!UCuGm%td^|c`S^uQ`;CtMD~nFyL)b%!0bD2fy% zfpej(LbJg92yHW=1_jNqaY8w@(he20NPAhkEH)Xqcp#JomJN!0!U$?ui{zxsb%UU_YeE~f#kb@E#t_$}8JbmO3<;Iw0 zwGf^)cO26G5dK|{z`oSQ{5gX$B+1w?z$~%m@G8rnfvb`6nvO48acQUDR(83A-{lzG z=-czQItH;y?6B=L&qY`<(u?djffL zhx1AY^Gb*FRt@H@8aOtTw}Cgktm%x2&ppgH9On-|$lKce<_>6x z+4fomU$XksM1TJJE2aG2#=*))e@h$G#Ib7?&=`*0gpI)$w1Q(R@a1GHu`$~e_hMt5 z?7fPS?Be0<)q~lqc|!*Bing8H#%I;?2iy4C2lx!mZ*W~R+qyQ7sPuB7?@m6x+OOI* zX<)Llc~x2@8I$71b)VJJM)K}eKFi`e( zQGX`CYA2ss?KkWKZi{7}KXJa$Q(r+}VP87GXg_Z|;5XktVz!*sozeBK_M3|WW-BC4 znLU(T)V(o~Kev0M-;fi?g@E6X9mpl&>_A=#hHZhFi}0Nn$j-xeMquuIe9sQdF2;93 zAU(f(<0+If8!f|z+(ARGZ)Jay->@`bNR+}BzhQ|K=JQqzK!exHBJLS*`wiu?GWfjm z%MQPx>Y7*xpSN@%*>6}wc}fx-___DmkF<$UIPzj;F-C9~JvQ1&9NfLU zt;TQOH)6Iyk5d2Dyl}Eifpg~8K?S3?^fdG~_U=P&<)-`jjkSF3A>L9yq_guX`?t3i zGZx@Dve3_n3A4+;doN#ecxZP6zv~EZYaBK=4Vs(2n^a=j??7$Aocax;r}GgRq8oji zeJlFx{YwY7zrCV=F28!We@_j+Z~xGq1N`p$`7|7zfGHWKbbroZ>cXq0g@KHszT*S& zZ#*=Zv7$#C*5&?`fz$y#ziuC&x!-R-AkNz5Uz%rN*Ntb4zSusk?;(CcHJ`Q1Z{GbM ziJ2pod|yp}s^79AU{3mrk%^!A-J~8Gc-w_%%%@3ZDO)oXznNQ}2d~fL%jRs!ReW*J zEC~E=)(miepR3)Ps{VaZ{#J|n4< z7s&F%lkMh*;Do7&LOAkqOA*o7Te7c+)kQoa!LXP}MVcf+EtCuu0}o3KJIX+;SJ*+} zG{iJSf;5kr&$!s*K%DMnSomvKQnk54l?Pc;RMs%@=7q7-|44WB&TZ=|Yj;-fSYNh95`KVQPN5>GYHQ^{yT;{0 z^xS5nr$}6*s}|#H8=bWvn&KXTZ)*XYb!`Ms7c;6goz6U&`P{Ly51)DXwPV8t%LfaV z4{1v$8CJbype|rCpZ1>g_U`M;=QB!%O!NB722G3qs)Ub!#S}2b)k|)>^ib9>lgkp7 zA1A8IGGe0-;d7AaEGYfTG#s&^5f9bBd|6>veE)!b|JS#aZwiA91E2!h87C5XfL>SP z(6p;0fwG!NV8>tX8h_^G#4j&STzuvFM=wqEJ=I}$*SU@!ZES9~m29w;Y~Et4E>r^g zbi~oz3p^N8R04h!j0MTi)?HOqR4Bs+4Z>HVVC8~A zV1z+R#7b&i?JhttYK27Hlkj~bj?^0PfZRo;QiC#2!s)n^aeQiF-<-ZIK4sZu)$eMC zx6}@9sr4V>hL3a%9_jd3%|qXgpiU2~Hh!fsz|NvJ-gY6;@#wM@%tdWktn%X+7ToB= z^gdK6Uui&0V@u)Tmb-B+lBX%k8lpoNp`tCVBv>UvIlvB(^kSmv!Gzf9pmwh&4CI)X zm7T1tb`&W@Jizb(ogKL@3QRTZHF=s4&x&|}-LDDhyEG+4e1_one|<|Enq+rrn=aJ% zb^}aDi&z&-$J|bZEO#`viXOF#7`4_LhcVX;9P|LkEcK%o6U9S_Og>FkWN(M>$l0?6b4?I56^90Dr?T%u{ zgO1|j;@hCMNCOCHc(Xa-Cf%!fmS{9!RW=;P<`R_nHyb?Kr1JHesb)? z#{~ks|Luv3KZ7O`CDE|rZFG5yMii)`;z&BD&D%quh}}HN$;)jYYIj*22s6lQpZ@bdhNZt0jJ~R*LPKnQWTg^ zoYj}sXW-)(`&COO6*1~kC|sL3tg#Miti5S{i!SMgmsJcdtMKnUFuc<-xYO~a<}lU@ z>WO?(<(HZ&aT5`h`*`c3{_Xwi_~d27n&pF<<%qMWOK-c72k>!znQmq5;ajl9)IX zPrf@&<`!Kj;)~}+Mu3_>v4I3HZHz`N{oj> zTy0?Q;|K}(#W^Sn_)!DAmKM&PgPI&T2V23@#axTe^)2$vzP)@xNmu1H zbNV+-tUA711#?LoA4tmVspxh09_ABfbgvsp$v*2n z+fXhpMOJ&X7RzLch&+a*)6t~SU{@q84dC4??Ai|Kk6nio-Nl|-cbA&Tv0^t6Ro5`t z>8XPt2MHbZQI1Z#BtL*QnhMQ?VLS2a`O%MF7MF;$ghbN-krhhpAI5+2D&^sUZk({4 zpZ^CBo*}{>FJy<^{al}TEFmrdn>Q9zO*DF>v_F%j!u?1{g(ugEC%}}1Rsp90VOtV* zt(2FTDGRmik$ocW7OOKGn%GE-l1Su!S2|Z}Up3=gB842avX5D5>ZnK4+ zJ$H14RB`>?$H&jU3&mc)@E#=vY<2X=93#gWB?+K6GzdKf1+a;i-T|`;+VTJFdUQjf zmMmI&V3EU#oLac6a!u%jjxPE6q^ImGqLdb78^ew#?kMI%2PduTBk;g+q+#H|S>%tk zi3dZ<{Qms@a8Z>jLBDo0c)y(+&d6A1Xk zq^`;lV?xi0A!9afB2{cRJEG6(b^G=C0*8Q4T?ZVBuM$&6Lnm86Z|HvD&Pr%J_C z=}QOLe%C4HBT8jaVix`c`n>u!=z^3XT1NEF&_*OM~t zw%dYYJJG|Cq5AD{p9gj3g(^#Cl~|xOyl8p?l}?=5F6w! zcyhoAtB{B%=}wO%17XB?aPmu=ezhkbg5&4j0(nZ57N7f>*xVPll4jC0(#|f4e4cn* zUa?RUQm&&Hp9bL|Ed_`VvRkA4g@|3a=h27gANM10f~qE0tCJENJ24XFnN;9H_I8RD zQYdICdWSfm8DIcR9~yx(fO`q^A%=SgR+vPj5n$mZ5hs|Jdjrz;z>0L`fdl*pGn1I_ zD+f+$7H?kGwRtop{cQW0_GcdgiG#p)=M&dmF2B6wN;bduAb(#SUuWmz!654Js4nhw z;mN|Mig@d+LEWsrrB`(eCKX0)B|Azn$ABd>U`-9AWZa5XS>w7l0mI&Gm7$P*4_@ee zqw~!l4414OELnSb*-%LpfA99ICEEq=CZE0LvgLBjVJ1n_G-Ha9vl`l2Iqn-WVsaJBYKK!R@*xIMnh^FH!hoaUv9O2A z&kPoH1eNZ1u}siB5>5udthWGWt%9==iAnNI*>@cfhWZXj180%}NlDNUl2o;Ii5wuyPsd6|1y#W>tU~3|k)u9A~b;7VCTtBL* zVDsRvWRvSEc$Pzq(5Ba3M9mjj%R)K`#6o54!lmmc-nstIkBz_IGycoR#xI=)8T@w0 zPgD_Pq)Ii$5qhNreTW=Tq6Og>Vye+|jvS3e@FA$F=Rng1437(AL&gnU4StG8x`t=~ zh!T3vjb-6s#~lO5MF>a4y9B8S=fqFwyn_eaDe-{2yJ3SUV6Ny{$!E{!;}`f<3k72b zV4U^pdc1sI8K1P)uUQw+!~u0=NbFhCyTq^06*+pPy>s}PAjI3p?>{uOr+#=(^WdIl zes>FRb^0}}km;H=l~*N!fdQX7dnmE!8zxSDltnSMQ47*UK}=oIyOJ+hdwJ=V?H{kW zJom~2{Psiqz4d&(olgb9?%{wT;dJH6%2Qjy(y!!G@9`T}ePu}avmOim7G+~}1x><@(W@*=#sy~@uwtD?y^=}rdArN+yN<`grFq$Y{0*hGiMLx(|1i}g! zBjidDN$^C_{<~Pq+T}-}bQIlz_A5G-4=dZ1cJh{Oaw^ZiL2Jk+hnOPRD-zu(fzk&~ z13~Y}ce)c!(^yeZkRie(Nds_N7h{lWxxV3F0&Um41hU%xnHv$ z&2Rv#QzzYaS)i~-&56vjC^tKoQ>IXUtWcxy3|k{Udhif@?jZ2r$RuR%YU2XTZ*D>8 zECmB`R%BswGhq@(;Y>!Q(It_zV7jg(^sb7Su7e$lQ(;GI6l@`RqM3`MT?gE1geL5< zgyk;b8^Bd$xmLK1NMqN6xJ+l)116svQYws9i9aw@y5la6ZD<&gsj7w#G zjfT17E|3Xz-gS(fNwSinjFUYpG|FTwG!JWf*i(5*I5Y+LUM4EXySxfC43K=BtLEcK0M<4?GJ#v7P_A6 zdoR}_YRFV282Pa>Y77NJlmNN2r$E9WIRsYI_CpIfQNDvH7+-^`q2Q?}jsu-#m6-;o|a}I~Iw)OB~>HaHr9^vpp zdj$)D^B+!paDMFV(__DSe!PDG=Ba_c#PK~%+Db=jO<$<~?sAT{?P#W+wyFrN!14LIn z7~2Sn?_8}TsKjJ3LJDyh1i1n!B2EklX^dhxr1VZS&=7r>Jw!#3Yr!_7Gl|&y8bG80 zdrzoj)n)8gChLf`VA#5B(7LR9GZ5O(HS`|#IsM6t1Jt5bnUD(yenSbTJD;0dw9>3;3)R#{J^4hqU z1!-kcTJIuXYQU1-TkTsJu-GPJRT&9Anp-+1yYLm~3(mg#F5CI-yZG8Vf7YQMeIPMw zII(arv9NFZ)x=^^NtuwfJNcaIFD<)JEqU9SZN3g)GrwT-m7?K_{eutW2IGSmKS7v|G%CFze+x85b?;AAVM>L@Kf(EpOuq5IE zCdj)0J^$X0-e!LG4*$;G{GR)VcGmJc4)O;dxXv01ak1U!^7r=tL6sM^COT=2SbQAM3k|}D}t-ocIZUudAR>RK6#a2vs#Gn zje9NfnzdognnBGPLbR3v1LZkbO^=opRGOHNXO|UkNMb%oUAQrh`P86*s83C0^D2^< z->go8z;BbZ8}-WHX60>+QGUj%A?C9fHN1UMf(WQE+FVNTySg!K(p$QD*0DH+nUA zr|YzGhuv||!#dYaE0;;$dXR2wVZ?N#UY0u#w$xf8g?K|Qn+b#bDp@VlgnV?k!$o-^ z7)4VBrZXd6-d=z<;wur4hhVdDm*1`@tXovxig-d-=|z=IDMYSWC-OwHBQirUG{&-4P2FoN((Uqzyw~tp8`GIscThcF(x`EQ|u55?QOKhR=WVgmC7_ zO8>iHB93vpUi3v9ZHhy3iXQc;JlLULE1ceCid}#2m)Add3$73t9k_Vo)qlS6 z@=rkDcjD1sjh(nScHvjZ;{mk;kY#uO?E2|*V^2Rne(L0nAD+j{IR<`8E%?XJp%IO! zG)D@ZE~`y4I1k+~O7gqw4jlqQ`;f=&#=F;nGX;#eI~oBkgWEenFOB4pDf5b|eM0*{ zYfXN~{+)t8fmK3ovlqpBgmDw8(nnZh^coFBrtu~Ws)#^uUPp}p!c7`K`8sMUlNOIZ z`}F7s=R8H2{*K!dA}`LM8VqQqgQ_?-8pLkEMe>C8M)VxQ;zec+(O=lQ!GqYEaKVJg zDJ!*gB!?J|Tk1lW`Fl{S(+1ok(6E*!fM-Lf2vpFJeL{ZOhd-Nm<^r7|6h7jVI0bsy zFp=cxZ{T@%XJ2TWiORa|&XR0I@7;#b?0E)jBv)yKIzw;RzcZhV)#NF}E>O-I{6v9# zdPge|F<~231YICk_`rcc4wCKqhaW;2RQXaqtDvx`l@O*O!j+DixSk@H5pW@qqbtAw zdK=l*YN7@sH0{n6U@P!wAb)|4Dq9MPg~c0L86*b$1`^BRl#A8hInAJ76Bl2fxcGD0 zS$b=UFnh_BzHk$(&?&M|5U51$1*G`GF7l-B`UfA}ID7uaPkKECp-$4tLMH&$LQK>I zpyP6)S~oEEv}POu0-~u#z$R+OQs@XX3$=mB1ft7P)c7g26~@a{Z}ved%2Yg#zB%a3 zMF%;HAtqwbEM8VBatwni@+$y_!;SFM0}lEVKpYCdgNn`NL3Mp|WBpMMN#w$~5Z5u7 zaObwh`7o0nZ^ms0t0s>V7>@X}2J{mDAX`h7c2K?%G~*73^@2q#nAAF4L31cXGjpF{ zLStAxK?4>jw%mmR1sTT?ywol_y`dGZ69J=W()il2BDer#+5pF%Ycp(nz>G{|5-nf@ zrAzA759#tI7457xQ`D@X7e?&rUvWA9?bZB(b^N+oKJ}pAP&b-5(|4?|-JiHT;tGIT z-geM$uA5XC;%AKJFY4cWIc6w-#V-ALF-)52OhCzyrO$S=bL{He+^*Hg5jdIgGFn}hwN7jFBp8s``Y`C z^2@jITkq$$z%2{ylMM3^y&t3lZYhD#%q@yr43oN5F`3S!l*k)C1P!0XSmy8x*9|7F zL$D$vwxO@FZy&#`jz7flbuK>N?YDTYS+afe2QB$Mm4TF8-@dCU^G2*;_20|S*zULP zkTv%!t8En07+)~<#rA9a9^y-n@-v(L)|Nna{-iG7=wSmeUbcmUwuSF(dav^BO1^C8 zkgdAMc+H$SY|a}r=lRVuB1Q<Xseb7SA;z7_nE%HOR6?ddNzTv^8FR{Jfx z0#W)d4a(Qh%k2E`z{)0m#&@?iu}sDc;8DU{fUcWfF!dGm7xt&~D{K7w;QrggL;D)| znj`$IM!&V`YilO*Ba^a7HVa(O%Nm;rLsWLp1Ptpzeha9IH}Y9c|7t!ul9=%glK`;8 zE=Eh|^tW=n%RSWU;ho3$tmA(3g8(OMQQwmO<^Gg=BF4C$&$s(6j**mtK5hRJe@bb@ zsP5xu?Dty_T(jo+9vHOF>eUC*XZEFEOCU#0xmz)2*G2&{Dsw?}hY~dkw7Annw()?sB6QA`hu#c%OAK95gSBE(I zD=+7~y^UX3&X@1?*Bs#QuN|s6$nUM=(+>F!^#Mc5_cw17pSjs@u9$9nMo@_#+5?w_ zXtbHPCml*gpWLhW>+=5Q`U7mMPnM~6=$VgAvn#+|UN#2;pUhmFRXLmatx5}l-^P`t z!0WSuRB*2-*2Y%NU_RGtD+`pLTgsG`xymn6uz)WziJPm24__20F=n>53cfDPugX;Z zE+qzHewV3+nBV2%*Wb;Uac`0G_c{gme{ayB-?G+x?@Z;tHY~T zzs{gr0Y7q~cueDE@BAT#oglpS5StI)=u`r*XIm+3w7g)v0?`1(iZ z;O+`QZjny81nJ(STDzm~KQTJcH97#;EFg9#fta90kW3TLy*>K=GxR34>*rscIDZy$ zPrQC%{Kt>bDkQ{@X5#iji-HMT9mhkLkgq5ATUEjbt_qwm&>&bA!vEj#a{}BNNY9ml z1C~DP(2uB!_=^H?hgp<+=L@O5i*R8V{Z)$>>5%6yJ)Pp{T* z${CC+`y00r>s3(1XgB(mP_y)7i1Cp%xVRBWB(!J^#W0eiH-`InNI8VngEA^!s6J{= z?AiQmT-WAr6tSr>p!$0YY@|&?ab2bkiLvpm2p3T#9<(n6kl_Jw7vViDsk;^AlAmb)=*Yzw$cuyvGD2LzLIYKqKY=$L%I&h?7s}x77L9n-ONXN5aEG#_ zv4uMYwN}F%cn99}G73b@xu?PDutdAtqL8J@K8F-UI{;AnJ1Z0ES5Sr3gyjAq?3;TA zYeC#RsBYsxIfUxQ3U^GzfyyiX>Xq;d6;bY)NCIS;*nSBq>3x0NC=_@NXWhf#0hCr{ zRwoI!$y`lY2|sStZ~2Ro)h?A8-~!5#G1F)F?dFSC^QmjP;TNJ3g!?11`rKb?7QyK! zNP8EYio;9z$+a^?uhz($=e||npY!J7zWeyNC4S9PnNBTgqC8rbQTQ_hh?SxJ72N>0b&Tqg8Gv^M*Q|)M+D&kFzM{+22jZu<9iEp@rLIS9^3pSuXRAwJcfru|W&o zkCWA9*|8s|E76}#++0Q3Ol6_swlVC78*itC_%>m`!bnboLgiL&0{eR%oI)3RJ6K?iDfW`E)e=-d4d&w@p177c8hU^wK|Jw zqC@SJ+|8yzSuz??i>nNNy^=#V8T^7DS)|3nZypxI5BA|tGm;xJgPATMpq(%?^!{Lq_RIFbY3?&kpDxFU*LW^@c?7bcx1LjE)eon7A;&)7_eo`r> zBMbt<8+ZhRPomR<&N+0BqVoniPIOS;p6c!(xlE2VvdeHd(a6C~4lQyR-qx&aX|;Qr z9jmyT@D1gw0~qwUo#nBGmF1wmVeDG`I=e! zHM8d5nTLm%hred#-;60`<8D64ma(k$<|dH!vJUo}gK8zaJA*yS-n^HMXOnKus%9bc&1OZVf{p*CMx|%hvNzKb*rZ#tV%a3p>i-An C(~GA7 literal 0 HcmV?d00001 diff --git a/globals/__pycache__/ex_apis.cpython-312.pyc b/globals/__pycache__/ex_apis.cpython-312.pyc new file mode 100644 index 0000000000000000000000000000000000000000..ce0fc74506fa738a3417c93da602c8066c75fa32 GIT binary patch literal 12072 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 literal 0 HcmV?d00001 diff --git a/globals/__pycache__/global_variable.cpython-312.pyc b/globals/__pycache__/global_variable.cpython-312.pyc new file mode 100644 index 0000000000000000000000000000000000000000..6e5b65c30bdabcf365ac808ecaca232806beb6c2 GIT binary patch literal 611 zcmaJ-J4?e*6h3KvKTxVIYO#tUsDp!(iU={elxUiS+#82bxYVYEN;J}1rJej2uCD$T zm$-#Yf|Hxzri&*v#TR(sJMZ(|bMF24_z0kEzjx|mX@I9V=t(MZUboP>0S9bw1Y1Cx zLZqFPlXg>fp9^+>9qa+LgJ_2k!-x@#N85dC{Sic?)lR$B@z30=+Szl=`iyUZwl)?` z7FB&;RJlx!6p3&}jwX_fMTx12B@*S6gk+f}(HJ*O{eVb}cZP53$J#nkNb?*5U zr|f$Eam8yUmS0vH^~$kV+c#U4MX;#uRdX08ERrivR!s literal 0 HcmV?d00001 diff --git a/globals/__pycache__/ids.cpython-312.pyc b/globals/__pycache__/ids.cpython-312.pyc new file mode 100644 index 0000000000000000000000000000000000000000..e351c539940a961ffa320d1dfb4ae8f25ef94cb0 GIT binary patch literal 2616 zcmaJ@TTk0a6b_}OTyrOoLNByH3oWp;+HTc$yXz#=64h~{#K3lhG%}eHm^yJ}PoVfI z`z!W0thE1RUwE|qy6>w>ecHZs&p2@ux?Z^!!+k;p$n z2!12I!-=yPsoKD~gI*BLg6rQ5f zc$&`O85+e=8pAOf$8nm#37W)7n!+iX#%Vf>XK4m!=p3G-^LUdRVgS(4xs%kI)G~D{lCGOg&cIz_5Nq4qYz(PTZyb?rVrb`Pn}0|B zEKolVMYoy3>YVuLlU_YsZxMbvq#vRJV(2z^?t~oaoHg>0w+0-`$%OP==LU1KA>4-W ziWp8(_K9yhYgf639L)yfmR=`}JL^|bp~)>~XI z+$SOKrFohb6_shH-5$+`*kXN$G~VB8)}+ot$l~mnF=G#k+`dZr^eW}EA=_bo%nco4 zU2E2vA#~vz*c~F=6}Y7Alo|dW+z(~kr48;%co^K|nn7%?aoubZ_NfH}^!GeW6-z1Z zr)5Gw2JZ%9T150J{Br5Gzq|P1^t=1n(sYYaLfNTaXSQ=Uoc?7(csqj1EEb$JquYzk zkTb2#&Hl9!w4h|MrOMj67nRHJiVE4Sp{gR4P#{;NT5Z2vEqD`MeNI)p_->^jsfYmn zp5&w&@)A8nMtevma#2BEHUyD;S$SKmmb__6Mpcy*iW2w~fuurFc^AmZLU~^i7*VBM zPO6e(!Aq2oRNJkh0VoMuL^8*DYWq?Jl`5+F z?ZdgdFsjR3w z-c%qKqz_&!5Q4Eq`+%h6k?f^Fp@qwK>L#|}G5x4B0YLiIMI`Q~pI<~0?&6OZkt|^G<<7L5 zSnfpOoB=H9rk{acnEwKn$<0p8&45M9o!h#Iq}}8O0RAKWQld^4Crb{6<$glkU`NXWWfFf!Ohl&Wsy<+?jHd>tLMN@Kc$4BkWQT oBJO6k#d+cFMP%NMzXEoA6CQ~dqPnN>h`$DL?D_Ab)&^AnKf#N7F#rGn literal 0 HcmV?d00001 diff --git a/globals/alarm.py b/globals/alarm.py new file mode 100644 index 0000000..447bb22 --- /dev/null +++ b/globals/alarm.py @@ -0,0 +1,88 @@ +import subprocess +import os +import logging + + +def push_audio_to_phone(device_id, local_audio_path, phone_audio_path): # 移除默认值 + """ + 将本地音频文件推送到手机指定路径 + """ + # 检查本地文件是否存在 + if not os.path.exists(local_audio_path): + logging.error(f"本地音频文件不存在: {local_audio_path}") + return False + + # 执行ADB推送命令 + cmd = ["adb", "-s", device_id, "push", local_audio_path, phone_audio_path] + try: + result = subprocess.run(cmd, capture_output=True, text=True, timeout=30) + + if result.returncode == 0: + logging.info(f"音频文件已推送到手机: {phone_audio_path}") + return True + else: + logging.error(f"推送音频文件失败: {result.stderr.strip()}") + return False + except subprocess.TimeoutExpired: + logging.error("推送音频文件超时") + return False + +def play_audio_on_phone(device_id, phone_audio_path): + """ + 控制手机播放指定路径的音频文件 + """ + # 检查文件是否存在于手机 + check_cmd = ["adb", "-s", device_id, "shell", "ls", phone_audio_path] + check_result = subprocess.run(check_cmd, capture_output=True, text=True) + + if check_result.returncode != 0: + logging.error(f"音频文件在手机上不存在: {phone_audio_path}") + return False + + # 使用am命令播放音频 + cmd = [ + "adb", "-s", device_id, "shell", + "am", "start", + "-a", "android.intent.action.VIEW", + "-t", "audio/*", + "-d", f"file://{phone_audio_path}" + ] + + try: + result = subprocess.run(cmd, capture_output=True, text=True, timeout=10) + + if result.returncode == 0: + logging.info(f"已成功触发手机播放音频: {phone_audio_path}") + return True + else: + logging.error(f"播放音频失败: {result.stderr.strip()}") + return False + except subprocess.TimeoutExpired: + logging.error("播放音频命令超时") + return False + +def play_system_alarm(device_id): + """ + 播放系统内置的警报声 + """ + try: + # 方法1:使用系统铃声URI + cmd = [ + "adb", "-s", device_id, "shell", + "am", "start", + "-a", "android.intent.action.VIEW", + "-d", "content://settings/system/alarm_alert" + ] + + result = subprocess.run(cmd, capture_output=True, text=True, timeout=10) + + if result.returncode == 0: + logging.info("成功触发系统警报声") + return True + else: + logging.error(f"播放系统警报失败: {result.stderr.strip()}") + return False + + except Exception as e: + logging.error(f"播放系统警报时出错: {str(e)}") + return False \ No newline at end of file diff --git a/globals/apis.py b/globals/apis.py new file mode 100644 index 0000000..223ff0d --- /dev/null +++ b/globals/apis.py @@ -0,0 +1,479 @@ +import requests +import json +import logging +import socket +from typing import Optional, Dict, Any +import globals.global_variable as global_variable + +def send_tcp_command(command="StartMultiple", host="127.0.0.1", port=8888, timeout=10): + """ + 使用TCP协议发送命令到指定地址和端口 + + 参数: + command: 要发送的命令字符串(默认:"StartMultiple") + host: 目标主机地址(默认:"127.0.0.1") + port: 目标端口(默认:8888) + timeout: 连接超时时间(秒,默认:10) + + 返回: + 成功返回服务器响应(字符串),失败返回None + """ + # 创建TCP套接字 + with socket.socket(socket.AF_INET, socket.SOCK_STREAM) as sock: + try: + # 设置超时时间 + sock.settimeout(timeout) + + # 连接到目标服务器 + sock.connect((host, port)) + logging.info(f"已成功连接到 {host}:{port}") + + # 发送命令(注意:需要根据服务器要求的编码格式发送,这里用UTF-8) + sock.sendall(command.encode('utf-8')) + logging.info(f"已发送命令: {command}") + + # 接收服务器响应(缓冲区大小1024字节,可根据实际情况调整) + response = sock.recv(1024) + if response: + response_str = response.decode('utf-8') + logging.info(f"收到响应: {response_str}") + return response_str + else: + logging.info("未收到服务器响应") + return None + + except ConnectionRefusedError: + logging.info(f"连接被拒绝,请检查 {host}:{port} 是否开启服务") + return None + except socket.timeout: + logging.info(f"连接超时({timeout}秒)") + return None + except Exception as e: + logging.info(f"发送命令时发生错误: {str(e)}") + return None + +def get_breakpoint_list(): + """ + 获取需要处理的断点列表 + """ + # 请求参数 + params = { + 'user_name': global_variable.GLOBAL_USERNAME + } + + # 请求地址 + url = "https://engineering.yuxindazhineng.com/index/index/get_name_all" + + try: + # 发送GET请求 + response = requests.get(url, params=params, timeout=30) + + # 检查请求是否成功 + if response.status_code == 200: + result = response.json() + + # 检查接口返回状态 + if result.get('code') == 0: + data = result.get('data', []) + logging.info("成功获取断点列表,数据条数:", len(data)) + + # 打印断点信息 + # for item in data: + # logging.info(f"线路编码: {item.get('line_num')}, " + # f"线路名称: {item.get('line_name')}, " + # f"状态: {item.get('status')}, " + # f"用户: {item.get('name')}") + + return data + else: + logging.info(f"接口返回错误: {result.get('code')}") + return [{"id": 37, + "user_name": "wangshun", + "name": "wangshun", + "line_num": "L193588", + "line_name": "CDWZQ-2标-155号路基左线-461221-461570-155左-平原", + "status": 3 + }] + else: + logging.info(f"请求失败,状态码: {response.status_code}") + return [] + + except requests.exceptions.RequestException as e: + logging.info(f"请求异常: {e}") + return [] + except ValueError as e: + logging.info(f"JSON解析错误: {e}") + return [] + +def get_measurement_task(): + """ + 获取测量任务 + 返回: 如果有状态为1的数据返回任务信息,否则返回None + """ + try: + url = "https://engineering.yuxindazhineng.com/index/index/getOne" + + # 获取用户名 + user_name = global_variable.GLOBAL_USERNAME + if not user_name: + logging.error("未设置用户名,无法获取测量任务") + return None + + # 构造请求参数 + data = { + "user_name": user_name + } + + logging.info(f"请求参数: user_name={user_name}") + response = requests.post(url, data=data, timeout=10) + response.raise_for_status() + + data = response.json() + logging.info(f"接口返回数据: {data}") + + if data.get('code') == 0 and data.get('data'): + task_data = data['data'] + if task_data.get('status') == 1: + logging.info(f"获取到测量任务: {task_data}") + return task_data + else: + logging.info("获取到的任务状态不为1,不执行测量") + return None + else: + logging.warning("未获取到有效任务数据") + return None + + except Exception as e: + logging.error(f"获取测量任务失败: {str(e)}") + return None + +def get_end_with_num(): + """ + 根据线路编码获取测量任务 + 返回: 如果有状态为1的数据返回任务信息,否则返回None + """ + try: + url = "https://engineering.yuxindazhineng.com/index/index/getOne3" + + # 获取用户名 + user_name = global_variable.GLOBAL_USERNAME + line_num = global_variable.GLOBAL_LINE_NUM + if not line_num: + logging.error("未设置线路编码,无法获取测量任务") + return None + if not user_name: + logging.error("未设置用户名,无法获取测量任务") + return None + + # 构造请求参数 + data = { + "user_name": user_name, + "line_num": line_num + } + + # logging.info(f"请求参数: user_name={user_name}, line_num={line_num}") + response = requests.post(url, data=data, timeout=10) + response.raise_for_status() + + data = response.json() + logging.info(f"接口返回数据: {data}") + + if data.get('code') == 0 and data.get('data'): + task_data = data['data'] + if task_data.get('status') == 3: + logging.info(f"获取到测量任务: {task_data}") + return task_data + else: + logging.info("获取到的任务状态不为3,不执行测量") + return None + else: + # logging.warning("未获取到有效任务数据") + return None + + except Exception as e: + logging.error(f"获取测量任务失败: {str(e)}") + return None + + + + + +def get_work_conditions_by_linecode(linecode: str) -> Optional[Dict[str, Dict]]: + """ + 通过线路编码获取工况信息 + + Args: + linecode: 线路编码,如 "L118134" + + Returns: + 返回字典,格式为 {point_id: {"sjName": "", "workinfoname": "", "work_type": ""}} + 如果请求失败返回None + """ + url="http://www.yuxindazhineng.com:3002/api/comprehensive_data/get_settlement_by_linecode" + max_retries = 3 # 最大重试次数 + retry_count = 0 # 当前重试计数 + while retry_count < max_retries: + try: + # 准备请求参数 + payload = {"linecode": linecode} + headers = { + 'Content-Type': 'application/json', + 'User-Agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36' + } + + logging.info(f"发送POST请求到: {url}") + logging.info(f"请求参数: {payload}") + + # 发送POST请求 + response = requests.post( + url, + json=payload, + headers=headers, + timeout=30 + ) + + # 检查响应状态 + if response.status_code != 200: + logging.error(f"HTTP请求失败,状态码: {response.status_code}") + retry_count += 1 + if retry_count < max_retries: + logging.info(f"准备重试... (剩余 {max_retries - retry_count} 次)") + continue # 继续重试 + + # 解析响应数据 + try: + result = response.json() + except json.JSONDecodeError as e: + logging.error(f"JSON解析失败: {str(e)}") + retry_count += 1 + if retry_count < max_retries: + logging.info(f"准备重试... (剩余 {max_retries - retry_count} 次)") + continue # 继续重试 + + + # 检查API返回码 + if result.get('code') != 0: + logging.error(f"API返回错误: {result.get('message', '未知错误')}") + return None + + # 提取数据 + data_list = result.get('data', []) + if not data_list: + logging.warning("未找到工况数据") + return {} + + # 处理数据,提取所需字段 + work_conditions = {} + for item in data_list: + point_id = item.get('aname') + if point_id: + work_conditions[point_id] = { + "sjName": item.get('sjName', ''), + "workinfoname": item.get('next_workinfo', ''), + "work_type": item.get('work_type', '') + } + + logging.info(f"成功提取 {len(work_conditions)} 个测点的工况信息") + return work_conditions + + except requests.exceptions.RequestException as e: + logging.error(f"网络请求异常: {str(e)}") + retry_count += 1 + if retry_count < max_retries: + logging.info(f"准备重试... (剩余 {max_retries - retry_count} 次)") + except json.JSONDecodeError as e: + logging.error(f"JSON解析失败: {str(e)}") + retry_count += 1 + if retry_count < max_retries: + logging.info(f"准备重试... (剩余 {max_retries - retry_count} 次)") + except Exception as e: + logging.error(f"获取工况信息时发生未知错误: {str(e)}") + retry_count += 1 + if retry_count < max_retries: + logging.info(f"准备重试... (剩余 {max_retries - retry_count} 次)") + # 达到最大重试次数仍失败 + logging.error(f"已达到最大重试次数 ({max_retries} 次),请求失败") + return None + +def get_user_max_variation(username: str) -> Optional[int]: + """ + 调用POST接口根据用户名获取用户的max_variation信息 + + Args: + username: 目标用户名,如 "chzq02-02guoyu" + + Returns: + 成功:返回用户的max_variation整数值 + 失败:返回None + """ + # 接口基础配置 + api_url = "http://www.yuxindazhineng.com:3002/api/accounts/get" + timeout = 30 # 超时时间(避免请求长时间阻塞) + + # 1. 准备请求参数与头部 + # 接口要求的POST参数(JSON格式) + payload = {"username": username} + # 请求头部:指定JSON格式,模拟浏览器UA避免被接口拦截 + headers = { + "Content-Type": "application/json", + "User-Agent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/120.0.0.0 Safari/537.36" + } + + try: + # 2. 发送POST请求 + + response = requests.post( + url=api_url, + json=payload, # 自动将字典转为JSON字符串,无需手动json.dumps() + headers=headers, + timeout=timeout + ) + + # 3. 检查HTTP响应状态(200表示请求成功到达服务器) + response.raise_for_status() # 若状态码非200(如404、500),直接抛出HTTPError + logging.info(f"接口请求成功,HTTP状态码:{response.status_code}") + + # 4. 解析JSON响应(处理文档中提到的"网页解析失败"风险) + try: + response_data = response.json() + except json.JSONDecodeError as e: + logging.error(f"接口返回数据非JSON格式,解析失败:{str(e)}") + logging.error(f"接口原始返回内容:{response.text[:500]}") # 打印前500字符便于排查 + return None + + # 5. 检查接口业务逻辑是否成功(按需求中"code=0表示查询成功") + if response_data.get("code") != 0: + logging.error(f"接口查询失败,业务错误信息:{response_data.get('message', '未知错误')}") + return None + + # 6. 验证返回数据结构并提取max_variation + data_list = response_data.get("data", []) + if not data_list: + logging.warning(f"查询到用户名 {username},但未返回账号数据") + return None + + # 检查第一条数据是否包含max_variation + first_user = data_list[0] + if "max_variation" not in first_user: + logging.warning(f"用户 {username} 的返回数据中缺少 max_variation 字段") + return None + + max_variation = first_user["max_variation"] + logging.info(f"成功查询到用户 {username} 的 max_variation:{max_variation}") + + # 7. 直接返回max_variation的值 + return max_variation + + # 处理请求过程中的异常(网络问题、超时等) + except requests.exceptions.RequestException as e: + logging.error(f"接口请求异常(网络/超时/服务器不可达):{str(e)}") + # 若为连接错误,提示检查文档中提到的"不支持的网页类型"或域名有效性 + if "ConnectionRefusedError" in str(e) or "Failed to establish a new connection" in str(e): + logging.error(f"建议排查:1. 接口域名 {api_url} 是否可访问;2. 服务器是否正常运行;3. 端口3002是否开放") + return None + + # 处理其他未知异常 + except Exception as e: + logging.error(f"获取用户 {username} 的 max_variation 时发生未知错误:{str(e)}") + return None + +requests.packages.urllib3.disable_warnings(requests.packages.urllib3.exceptions.InsecureRequestWarning) +def get_line_info_and_save_global(user_name: str) -> bool: + """ + 调用get_name_all接口,提取status=3的line_num和line_name存入全局字典 + :param user_name: 接口请求参数,如"wangshun" + :return: 执行成功返回True,失败/异常返回False + """ + # 接口基础配置 + api_url = "https://engineering.yuxindazhineng.com/index/index/get_name_all" + request_params = {"user_name": user_name} # GET请求参数 + timeout = 10 # 请求超时时间(秒),避免卡进程 + + try: + # 1. 发送GET请求 + response = requests.get( + url=api_url, + params=request_params, # GET参数用params传递,自动拼接到URL后,规范且防乱码 + timeout=timeout, + verify=False # 禁用SSL验证,适配HTTPS接口 + ) + + # 2. 校验HTTP状态码(先确保请求本身成功) + if response.status_code != 200: + logging.error(f"接口请求失败,HTTP状态码异常:{response.status_code},响应内容:{response.text}") + return False + + # 3. 解析JSON响应(接口返回是JSON格式,需解析为字典) + try: + response_data = response.json() + except Exception as e: + logging.error(f"接口返回内容非合法JSON,无法解析:{response.text},错误:{str(e)}") + return False + + # 4. 校验业务状态码(接口约定:code=0成功,-1失败) + business_code = response_data.get("code") + if business_code == 0: + logging.info("接口业务请求成功,开始解析数据") + elif business_code == -1: + logging.error(f"接口业务请求失败,业务状态码code=-1,返回数据:{response_data}") + return False + else: + logging.warning(f"接口返回未知业务状态码:{business_code},请确认接口文档") + return False + + # 5. 提取data字段,校验数据是否存在 + api_data_list = response_data.get("data") + if not api_data_list: + logging.warning("接口业务成功,但data字段为空或无数据") + return False + + # 6. 校验data是否为列表类型 + if not isinstance(api_data_list, list): + logging.error(f"data字段不是列表类型,实际类型:{type(api_data_list)},内容:{api_data_list}") + return False + + found_valid_data = False + + # 7. 遍历列表,提取所有status=3的数据 + for item in api_data_list: + # 确保每个item是字典 + if not isinstance(item, dict): + logging.warning(f"列表中的元素不是字典类型,跳过:{item}") + continue + + # 获取字段值 + data_status = item.get("status") + line_num = item.get("line_num") + line_name = item.get("line_name") + + # 校验status是否为3,且目标字段非空 + if data_status == 3 and line_num and line_name: + # # 存入全局字典:key=line_num,value=line_name + # global_variable.GLOBAL_UPLOAD_BREAKPOINT_DICT[line_num] = line_name + # 存入全局字典:key=line_name,value=line_num + global_variable.GLOBAL_UPLOAD_BREAKPOINT_DICT[line_name] = line_num + + # 如果line_name不在列表中,则添加 + if line_name not in global_variable.GLOBAL_UPLOAD_BREAKPOINT_LIST: + global_variable.GLOBAL_UPLOAD_BREAKPOINT_LIST.append(line_name) + + logging.info(f"找到status=3的线路信息:line_num={line_num}, line_name={line_name}") + found_valid_data = True + + if found_valid_data: + logging.info(f"成功提取所有status=3的线路信息,当前全局字典数据:{global_variable.GLOBAL_UPLOAD_BREAKPOINT_DICT}") + return True + else: + logging.warning("data列表中未找到任何status=3且字段完整的线路信息") + return False + + # 捕获所有请求相关异常(超时、连接失败、网络异常等) + except requests.exceptions.Timeout: + logging.error(f"调用get_name_all接口超时,超时时间:{timeout}秒,请求参数:{request_params}") + return False + except requests.exceptions.ConnectionError: + logging.error(f"调用get_name_all接口连接失败,检查网络或接口地址是否正确:{api_url}") + return False + except Exception as e: + logging.error(f"调用get_name_all接口时发生未知异常:{str(e)}", exc_info=True) # exc_info=True打印异常堆栈,方便排查 + return False \ No newline at end of file diff --git a/globals/driver_utils.py b/globals/driver_utils.py new file mode 100644 index 0000000..6954ad6 --- /dev/null +++ b/globals/driver_utils.py @@ -0,0 +1,763 @@ +import logging +import time +import subprocess +import traceback +import socket +import os +import requests +from appium import webdriver +from appium.webdriver.common.appiumby import AppiumBy +from appium.webdriver.appium_service import AppiumService +from appium.options.android import UiAutomator2Options +from selenium.webdriver.support.ui import WebDriverWait +from selenium.webdriver.support import expected_conditions as EC +from selenium.common.exceptions import TimeoutException, NoSuchElementException, InvalidSessionIdException, WebDriverException +import globals.global_variable as global_variable + + +def init_appium_driver(device_id, app_package="com.bjjw.cjgc", app_activity=".activity.LoginActivity"): + """ + 初始化Appium驱动的全局函数 + + 参数: + device_id: 设备ID + app_package: 应用包名,默认为"com.bjjw.cjgc" + app_activity: 应用启动Activity,默认为".activity.LoginActivity" + + 返回: + (driver, wait): (WebDriver实例, WebDriverWait实例),如果初始化失败则抛出异常 + """ + logging.info(f"设备 {device_id} 开始初始化Appium驱动") + + # 创建并配置Appium选项 + options = UiAutomator2Options() + options.platform_name = "Android" + options.device_name = device_id + options.app_package = app_package + options.app_activity = app_activity + options.automation_name = "UiAutomator2" + options.no_reset = True + options.auto_grant_permissions = True + options.new_command_timeout = 28800 + options.udid = device_id + + # 增加uiautomator2服务器启动超时时间 + options.set_capability('uiautomator2ServerLaunchTimeout', 60000) # 60秒 + # 增加连接超时设置 + options.set_capability('connection_timeout', 120000) # 120秒 + + try: + # 连接Appium服务器 + driver_url = "http://127.0.0.1:4723/wd/hub" + logging.info(f"设备 {device_id} 正在连接Appium服务器: {driver_url}") + driver = webdriver.Remote(driver_url, options=options) + logging.info(f"设备 {device_id} Appium服务器连接成功") + + # 初始化等待对象 + wait = WebDriverWait(driver, 20) + logging.info(f"设备 {device_id} WebDriverWait初始化成功") + + # 等待应用稳定 + time.sleep(2) + + logging.info(f"设备 {device_id} Appium驱动初始化完成") + return driver, wait + + except Exception as e: + logging.error(f"设备 {device_id} 初始化驱动失败: {str(e)}") + logging.error(f"错误类型: {type(e).__name__}") + logging.error(f"错误堆栈: {traceback.format_exc()}") + # 如果驱动已创建,尝试关闭 + if 'driver' in locals() and driver: + try: + driver.quit() + except: + pass + raise + +def check_session_valid(driver, device_id=None): + """ + 检查当前会话是否有效 + + 参数: + driver: WebDriver实例 + device_id: 设备ID(可选) + + 返回: + bool: 会话有效返回True,否则返回False + """ + if device_id is None: + device_id = global_variable.GLOBAL_DEVICE_ID + device_str = f"设备 {device_id} " if device_id else "" + + if not driver: + logging.debug(f"{device_str}驱动实例为空") + return False + + try: + # 首先检查driver是否有session_id属性 + if not hasattr(driver, 'session_id') or not driver.session_id: + logging.debug(f"{device_str}驱动缺少有效的session_id") + return False + + # 尝试获取当前上下文 + current_context = driver.current_context + logging.debug(f"{device_str}会话检查通过,当前上下文: {current_context}") + return True + + except InvalidSessionIdException: + logging.debug(f"{device_str}会话已失效") + return False + + except WebDriverException as e: + error_msg = str(e).lower() + + # 明确的会话失效错误 + if any(phrase in error_msg for phrase in [ + "session is either terminated or not started", + "could not proxy command to the remote server", + "socket hang up", + "connection refused", + "max retries exceeded" + ]): + logging.debug(f"{device_str}会话连接错误: {error_msg[:100]}") + return False + else: + logging.debug(f"{device_str}WebDriver异常但可能不是会话失效: {error_msg[:100]}") + return True + + except (ConnectionError, ConnectionRefusedError, ConnectionResetError) as e: + logging.debug(f"{device_str}网络连接错误: {str(e)}") + return False + + except Exception as e: + error_msg = str(e) + # 检查是否是连接相关错误 + if any(phrase in error_msg.lower() for phrase in [ + "10054", "10061", "connection", "connect", "refused", "urllib3" + ]): + logging.debug(f"{device_str}连接相关异常: {error_msg[:100]}") + return False + else: + logging.debug(f"{device_str}检查会话时出现其他异常: {error_msg[:100]}") + return True # 对于真正的未知异常,保守返回True + + +def reconnect_driver(device_id, old_driver=None, app_package="com.bjjw.cjgc", app_activity=".activity.LoginActivity"): + """ + 重新连接Appium驱动,不重新启动应用 + + 参数: + device_id: 设备ID + old_driver: 旧的WebDriver实例(可选) + app_package: 应用包名 + app_activity: 应用启动Activity + + 返回: + (driver, wait): 新的WebDriver和WebDriverWait实例 + """ + # 使用传入的device_id或从全局变量获取 + if not device_id: + device_id = global_variable.GLOBAL_DEVICE_ID + + # 修复device_id参数类型问题并使用全局设备ID作为备用 + actual_device_id = device_id + + # 检查device_id是否为有效的字符串格式 + if not actual_device_id or (isinstance(actual_device_id, str) and ("session=" in actual_device_id or len(actual_device_id.strip()) == 0)): + # 尝试从old_driver获取设备ID + if old_driver and hasattr(old_driver, 'capabilities'): + capability_device_id = old_driver.capabilities.get('udid') + if capability_device_id: + actual_device_id = capability_device_id + logging.warning(f"检测到device_id参数无效,已从old_driver中提取设备ID: {actual_device_id}") + + # 如果仍然没有有效的设备ID,使用全局变量 + if not actual_device_id or (isinstance(actual_device_id, str) and ("session=" in actual_device_id or len(actual_device_id.strip()) == 0)): + actual_device_id = global_variable.GLOBAL_DEVICE_ID + logging.warning(f"无法获取有效设备ID,使用全局变量GLOBAL_DEVICE_ID: {actual_device_id}") + + device_id = actual_device_id # 使用修正后的设备ID + logging.info(f"设备 {device_id} 开始重新连接驱动(不重启应用)") + + # # 首先安全关闭旧驱动 + # if old_driver: + # safe_quit_driver(old_driver, device_id) + + max_reconnect_attempts = 3 + reconnect_delay = 5 # 秒 + + for attempt in range(max_reconnect_attempts): + try: + logging.info(f"设备 {device_id} 第{attempt + 1}次尝试重新连接") + + # 确保Appium服务器运行 + if not ensure_appium_server_running(): + logging.warning(f"设备 {device_id} Appium服务器未运行,尝试启动") + time.sleep(reconnect_delay) + continue + + # 创建并配置Appium选项 - 重点:设置 autoLaunch=False 不自动启动应用 + options = UiAutomator2Options() + options.platform_name = "Android" + options.device_name = device_id + options.app_package = app_package + options.app_activity = app_activity + options.automation_name = "UiAutomator2" + options.no_reset = True + options.auto_grant_permissions = True + options.new_command_timeout = 3600 + options.udid = device_id + + # 关键设置:不自动启动应用 + options.set_capability('autoLaunch', False) + options.set_capability('skipUnlock', True) + options.set_capability('skipServerInstallation', True) + options.set_capability('skipDeviceInitialization', True) + + # 增加uiautomator2服务器启动超时时间 + options.set_capability('uiautomator2ServerLaunchTimeout', 60000) # 60秒 + # 增加连接超时设置 + options.set_capability('connection_timeout', 120000) # 120秒 + + # 连接Appium服务器 + logging.info(f"设备 {device_id} 正在连接Appium服务器(不启动应用)") + driver = webdriver.Remote("http://localhost:4723", options=options) + logging.info(f"设备 {device_id} Appium服务器连接成功") + + # 初始化等待对象 + wait = WebDriverWait(driver, 20) + logging.info(f"设备 {device_id} WebDriverWait初始化成功") + + # 不启动应用,直接附加到当前运行的应用 + try: + # 获取当前运行的应用 + current_package = driver.current_package + logging.info(f"设备 {device_id} 当前运行的应用: {current_package}") + + # 如果当前运行的不是目标应用,尝试切换到目标应用 + if current_package != app_package: + logging.info(f"设备 {device_id} 当前应用不是目标应用,尝试启动目标应用") + launch_app_manually(driver, app_package, app_activity) + else: + logging.info(f"设备 {device_id} 已成功连接到运行中的目标应用") + except Exception as attach_error: + logging.warning(f"设备 {device_id} 获取当前应用信息失败: {str(attach_error)}") + # 即使获取当前应用失败,也继续使用连接 + + # 验证新会话是否有效 + if check_session_valid(driver, device_id): + logging.info(f"设备 {device_id} 重新连接成功") + return driver, wait + else: + logging.warning(f"设备 {device_id} 新创建的会话无效,将重试") + safe_quit_driver(driver, device_id) + + except Exception as e: + logging.error(f"设备 {device_id} 第{attempt + 1}次重新连接失败: {str(e)}") + if attempt < max_reconnect_attempts - 1: + wait_time = reconnect_delay * (attempt + 1) + logging.info(f"设备 {device_id} 将在{wait_time}秒后重试重新连接") + time.sleep(wait_time) + else: + logging.error(f"设备 {device_id} 所有重新连接尝试均失败") + # 所有尝试都失败后才关闭旧驱动 + if old_driver: + safe_quit_driver(old_driver, device_id) + raise + + # 所有尝试都失败 + raise Exception(f"设备 {device_id} 重新连接失败,已尝试{max_reconnect_attempts}次") + + + +# def ensure_appium_server_running(port=4723): +# """使用完整的环境变量启动Appium,解决路径缺失问题""" +# try: +# # 获取当前用户的环境变量 +# env = os.environ.copy() + +# # ========== 仅修改这部分:Windows下Node.js/Appium常见路径 ========== +# additional_paths = [ +# # Windows默认Node.js安装路径(64位) +# os.path.join("C:\\", "Program Files\\nodejs"), +# # Windows32位Node.js路径 +# os.path.join("C:\\", "Program Files (x86)\\nodejs"), +# # npm全局安装路径(Windows核心,appium一般装在这里) +# os.path.expanduser("~\\AppData\\Roaming\\npm"), +# # 系统默认路径(防止基础命令缺失) +# os.path.join("C:\\", "Windows\\System32"), +# os.path.join("C:\\", "Windows\\System"), +# # 自定义Node.js/npm路径(可选,根据你的实际安装路径加) +# # "D:\\Program Files\\nodejs", # 若你装在D盘,解开注释并修改 +# ] +# # ========== Windows路径修改结束 ========== + +# # 更新PATH环境变量(跨系统通用,os.pathsep自动适配Windows的;和macOS的:) +# current_path = env.get('PATH', '') +# new_path = current_path + os.pathsep + os.pathsep.join(additional_paths) +# env['PATH'] = new_path + +# # 构建启动命令(和原函数一致,无修改) +# appium_cmd = f"appium -p {port} --log-level error" + +# # 使用完整环境启动(跨系统通用,Windows下正常执行) +# process = subprocess.Popen( +# appium_cmd, +# shell=True, # Windows下字符串命令必须开启,和macOS一致 +# env=env, # 传入补全后的Windows环境变量(核心) +# stdout=subprocess.PIPE, # 捕获输出,控制台不刷屏 +# stderr=subprocess.PIPE, +# text=True # 输出为字符串,无需手动解码(跨系统通用) +# ) + +# logging.info(f"Appium启动进程已创建,PID: {process.pid}") +# # 等待并校验服务启动成功(需确保wait_for_appium_start已定义,timeout有值) +# return wait_for_appium_start(port) + +# except Exception as e: +# logging.error(f"Windows下启动Appium时出错: {str(e)}") +# return False + +def is_port_in_use(port): + """检查端口是否被占用""" + with socket.socket(socket.AF_INET, socket.SOCK_STREAM) as s: + return s.connect_ex(('127.0.0.1', port)) == 0 + +def kill_system_process(process_name): + """杀掉系统进程""" + try: + # Windows + subprocess.run(f"taskkill /F /IM {process_name}", shell=True, stderr=subprocess.DEVNULL, stdout=subprocess.DEVNULL) + except Exception: + pass + +def start_appium_server(port=4723): + """启动 Appium 服务,强制指定路径兼容性""" + # 1. 先尝试清理可能占用的 node 进程 + if is_port_in_use(port): + logging.warning(f"端口 {port} 被占用,尝试清理 node.exe...") + kill_system_process("node.exe") + time.sleep(2) + + # 2. 构造启动命令 + # 注意:这里增加了 --base-path /wd/hub 解决 404 问题 + # --allow-cors 允许跨域,有时候能解决连接问题 + appium_cmd = f"appium -p {port} --base-path /wd/hub --allow-cors" + + logging.info(f"正在启动 Appium: {appium_cmd}") + try: + # 使用 shell=True 在 Windows 上更稳定 + # creationflags=subprocess.CREATE_NEW_CONSOLE 可以让它在后台运行不弹出窗口 + subprocess.Popen(appium_cmd, shell=True, stdout=subprocess.DEVNULL, stderr=subprocess.DEVNULL) + logging.info("Appium 启动命令已发送,等待服务就绪...") + except Exception as e: + logging.error(f"启动 Appium 进程失败: {e}") + +def check_server_status(port): + """检测服务器状态,兼容 Appium 1.x 和 2.x 路径""" + base_url = f"http://127.0.0.1:{port}" + check_paths = ["/wd/hub/status", "/status"] # 优先检查 /wd/hub + + for path in check_paths: + try: + url = f"{base_url}{path}" + response = requests.get(url, timeout=1) + if response.status_code == 200: + return True + except: + pass + return False + +def ensure_appium_server_running(port=4723): + """确保 Appium 服务器正在运行,如果没运行则启动它""" + + # 1. 第一次快速检测 + if check_server_status(port): + logging.info(f"Appium 服务已在端口 {port} 运行") + return True + + # 2. 如果没运行,启动它 + logging.warning(f"Appium 未在端口 {port} 运行,准备启动...") + start_appium_server(port) + + # 3. 循环等待启动成功(最多等待 20 秒) + max_retries = 20 + for i in range(max_retries): + if check_server_status(port): + logging.info("Appium 服务启动成功并已就绪!") + return True + + time.sleep(1) + if i % 5 == 0: + logging.info(f"等待 Appium 启动中... ({i}/{max_retries})") + + logging.error("Appium 服务启动超时!请检查 appium 命令是否在命令行可直接运行。") + return False + +# ... (保留 init_appium_driver, safe_quit_driver 等其他函数) ... + +def wait_for_appium_start(port, timeout=10): + """ + 检测指定端口的Appium服务是否真正启动并可用 + :param port: Appium服务端口 + :param timeout: 最大等待时间(秒) + :return: 服务就绪返回True,超时/失败返回False + """ + # Appium官方状态查询接口 + check_url = f"http://localhost:{port}/wd/hub/status" + # 检测开始时间 + start_check_time = time.time() + logging.info(f"开始检测Appium服务是否就绪,端口:{port},最大等待{timeout}秒") + + while time.time() - start_check_time < timeout: + try: + # 发送HTTP请求,超时1秒(避免单次检测卡太久) + response = requests.get( + url=check_url, + timeout=1, + verify=False # 本地接口,禁用SSL验证 + ) + # 接口返回200(HTTP成功状态码),且JSON中status=0(Appium服务正常) + if response.status_code == 200 and response.json().get("status") == 0: + logging.info(f"Appium服务检测成功,端口{port}已就绪") + return True + except Exception as e: + # 捕获所有异常(连接拒绝、超时、JSON解析失败等),说明服务未就绪 + logging.debug(f"本次检测Appium服务未就绪:{str(e)}") # 调试日志,不刷屏 + + # 检测失败,休眠1秒后重试 + time.sleep(1) + + # 循环结束→超时 + logging.error(f"检测超时!{timeout}秒内Appium服务端口{port}仍未就绪") + return False + +def safe_quit_driver(driver, device_id=None): + """ + 安全关闭驱动的全局函数 + + 参数: + driver: WebDriver实例 + device_id: 设备ID(可选) + """ + if device_id is None: + device_id = global_variable.GLOBAL_DEVICE_ID + device_str = f"设备 {device_id} " if device_id else "" + logging.info(f"{device_str}开始关闭驱动") + + if not driver: + logging.info(f"{device_str}没有可关闭的驱动实例") + return + + # 检查driver是否为WebDriver实例或是否有quit方法 + if not hasattr(driver, 'quit'): + logging.warning(f"{device_str}驱动对象类型无效,不具有quit方法: {type(driver).__name__}") + return + + max_quit_attempts = 3 + for attempt in range(max_quit_attempts): + try: + logging.info(f"{device_str}尝试关闭驱动 (尝试 {attempt + 1}/{max_quit_attempts})") + driver.quit() + logging.info(f"{device_str}驱动已成功关闭") + return + except Exception as e: + logging.error(f"{device_str}关闭驱动时出错 (尝试 {attempt + 1}/{max_quit_attempts}): {str(e)}") + if attempt < max_quit_attempts - 1: + # 等待一段时间后重试 + wait_time = 2 + logging.info(f"{device_str}将在 {wait_time} 秒后重试") + time.sleep(wait_time) + else: + logging.critical(f"{device_str}尝试多次关闭驱动失败,可能导致资源泄漏") + + +def is_app_launched(driver, package_name="com.bjjw.cjgc"): + """ + 检查应用是否已启动 + + 参数: + driver: WebDriver实例 + package_name: 应用包名,默认为"com.bjjw.cjgc" + + 返回: + bool: 如果应用已启动则返回True,否则返回False + """ + try: + # 通过检查当前活动的包名来确认应用是否已启动 + current_package = driver.current_package + return current_package == package_name + except Exception as e: + logging.error(f"检查应用启动状态时出错: {str(e)}") + return False + + +def launch_app_manually(driver, device_id, package_name="com.bjjw.cjgc", activity=".activity.LoginActivity"): + """ + 手动启动应用 + + 参数: + driver: WebDriver实例 + package_name: 应用包名,默认为"com.bjjw.cjgc" + activity: 应用启动Activity,默认为".activity.LoginActivity" + """ + try: + if not device_id: + device_id = global_variable.GLOBAL_DEVICE_ID + # 尝试从driver获取设备ID + if driver and hasattr(driver, 'capabilities'): + device_id = driver.capabilities.get('udid') + device_str = f"设备 {device_id} " if device_id else "" + else: + device_str = "" + + logging.info(f"{device_str}尝试手动启动应用: {package_name}/{activity}") + + # 首先使用ADB命令退出应用 + if device_id: + try: + # 使用ADB命令强制停止应用 + stop_cmd = [ + "adb", "-s", device_id, + "shell", "am", "force-stop", + package_name + ] + stop_result = subprocess.run(stop_cmd, capture_output=True, text=True, timeout=15) + if stop_result.returncode == 0: + logging.info(f"{device_str}已使用ADB命令成功退出应用") + else: + logging.warning(f"{device_str}ADB退出应用失败: {stop_result.stderr}") + except Exception as stop_error: + logging.warning(f"{device_str}退出应用时出错: {str(stop_error)}") + + # 首先尝试使用driver的execute_script方法启动应用 + try: + if driver: + driver.execute_script("mobile: startActivity", { + "intent": f"{package_name}/{activity}" + }) + logging.info(f"{device_str}已使用Appium startActivity命令启动应用") + except Exception as inner_e: + logging.warning(f"{device_str}使用Appium startActivity命令失败: {str(inner_e)},尝试使用ADB命令") + + # 如果device_id可用,使用ADB命令启动应用 + if device_id: + cmd = [ + "adb", "-s", device_id, + "shell", "am", "start", + "-n", f"{package_name}/{activity}" + ] + result = subprocess.run(cmd, capture_output=True, text=True, timeout=15) + + if result.returncode == 0: + logging.info(f"{device_str}已使用ADB命令成功启动应用") + else: + logging.error(f"{device_str}ADB启动应用失败: {result.stderr}") + else: + logging.warning("无法获取设备ID,无法使用ADB命令启动应用") + + # 设置屏幕永不休眠 + if device_id: + try: + # 使用ADB命令设置屏幕永不休眠 + screen_timeout_cmd = [ + "adb", "-s", device_id, + "shell", "settings", "put", "system", "screen_off_timeout", "0" + ] + timeout_result = subprocess.run(screen_timeout_cmd, capture_output=True, text=True, timeout=15) + if timeout_result.returncode == 0: + logging.info(f"{device_str}已成功设置屏幕永不休眠") + else: + logging.warning(f"{device_str}设置屏幕永不休眠失败: {timeout_result.stderr}") + except Exception as timeout_error: + logging.warning(f"{device_str}设置屏幕永不休眠时出错: {str(timeout_error)}") + + # 等待应用启动 + time.sleep(5) + except Exception as e: + logging.error(f"手动启动应用时出错: {str(e)}") + logging.error(f"错误堆栈: {traceback.format_exc()}") + +def go_main_click_tabber_button(driver, device_id, tabber_button_text, max_retries=3): + """ + 跳转到主页面并点击对应的导航菜单按钮(带重试机制) + + 参数: + driver: WebDriver实例 + device_id: 设备ID + tabber_button_text: 导航菜单按钮的文本 + max_retries: 最大重试次数 + + 返回: + bool: 成功返回True,失败返回False + """ + retry_count = 0 + + while retry_count < max_retries: + try: + logging.info(f"设备 {device_id} 第 {retry_count + 1} 次尝试执行导航操作") + + if not check_session_valid(driver, device_id): + logging.warning(f"设备 {device_id} 会话无效,尝试重新连接驱动...") + try: + # 重新连接,获取新的driver + new_driver, _ = reconnect_driver(device_id, driver) + driver = new_driver # 更新driver引用 + logging.info(f"设备 {device_id} 驱动重连成功") + except Exception as e: + logging.error(f"设备 {device_id} 驱动重连失败: {str(e)}") + retry_count += 1 + if retry_count < max_retries: + time.sleep(2) + continue + else: + return False + + # 检查当前是否已经在主页面 + current_activity = driver.current_activity + logging.info(f"设备 {device_id} 当前Activity: {current_activity}") + + if ".activity.MainActivity" in current_activity: + logging.info(f"设备 {device_id} 已在主页面") + else: + logging.info(f"设备 {device_id} 当前不在主页面,循环点击返回按钮,直到在主页面") + + max_back_presses = 10 # 最大返回键次数 + back_press_count = 0 + + while ".activity.MainActivity" not in current_activity and back_press_count < max_back_presses: + try: + if not check_session_valid(driver, device_id): + logging.warning(f"设备 {device_id} 会话无效,尝试重新连接驱动...") + if not reconnect_driver(device_id, driver): + logging.error(f"设备 {device_id} 驱动重连失败") + # 点击返回按钮 + driver.back() + back_press_count += 1 + time.sleep(1) + + # 更新当前Activity + current_activity = driver.current_activity + logging.info(f"设备 {device_id} 点击返回按钮 {back_press_count} 次后,当前Activity: {current_activity}") + + except Exception as inner_e: + logging.warning(f"设备 {device_id} 点击返回按钮时出错: {str(inner_e)}") + break + + # 检查是否成功回到主页面 + if ".activity.MainActivity" not in current_activity: + logging.warning(f"设备 {device_id} 无法回到主页面,当前Activity: {current_activity}") + # 不立即返回,继续重试逻辑 + retry_count += 1 + if retry_count < max_retries: + logging.info(f"设备 {device_id} 等待2秒后重试...") + time.sleep(2) + continue + else: + logging.error(f"设备 {device_id} 达到最大重试次数,无法回到主页面") + return False + + # 现在已经在主页面,点击指定的导航菜单按钮 + # logging.info(f"设备 {device_id} 已在主页面,尝试点击导航菜单按钮: {tabber_button_text}") + + try: + tabber_button = driver.find_element(AppiumBy.ID, tabber_button_text) + # 点击按钮 + tabber_button.click() + logging.info(f"设备 {device_id} 已成功点击导航菜单按钮: {tabber_button_text}") + + # 等待页面加载 + time.sleep(2) + + # 验证操作是否成功 + # 可以添加一些验证逻辑,比如检查是否跳转到目标页面 + new_activity = driver.current_activity + logging.info(f"设备 {device_id} 点击后当前Activity: {new_activity}") + + return True + + except TimeoutException: + logging.error(f"设备 {device_id} 等待导航菜单按钮 '{tabber_button_text}' 超时") + except Exception as e: + logging.error(f"设备 {device_id} 点击导航菜单按钮 '{tabber_button_text}' 时出错: {str(e)}") + + # 检查会话有效性并尝试重连 + if not check_session_valid(driver, device_id): + logging.warning(f"设备 {device_id} 会话无效,尝试重新连接驱动...") + if reconnect_driver(device_id, driver): + logging.info(f"设备 {device_id} 驱动重连成功,继续重试") + # 重连成功后继续循环 + retry_count += 1 + if retry_count < max_retries: + time.sleep(2) + continue + else: + logging.error(f"设备 {device_id} 驱动重连失败") + return False + else: + # 会话有效但点击失败,可能是页面元素问题 + logging.warning(f"设备 {device_id} 会话有效但点击失败,可能是页面加载问题") + + # 如果点击按钮失败,增加重试计数 + retry_count += 1 + if retry_count < max_retries: + logging.info(f"设备 {device_id} 点击按钮失败,等待2秒后第 {retry_count + 1} 次重试...") + time.sleep(2) + else: + logging.error(f"设备 {device_id} 达到最大重试次数 {max_retries},导航失败") + return False + + except Exception as e: + logging.error(f"设备 {device_id} 第 {retry_count + 1} 次尝试时出错: {str(e)}") + retry_count += 1 + if retry_count < max_retries: + logging.info(f"设备 {device_id} 等待2秒后重试...") + time.sleep(2) + else: + logging.error(f"设备 {device_id} 达到最大重试次数 {max_retries},导航失败") + return False + + return False + +def check_connection_error(exception): + """检查是否为连接拒绝错误""" + error_str = str(exception) + connection_errors = [ + '远程主机强迫关闭了一个现有的连接', + '由于目标计算机积极拒绝,无法连接', + 'ConnectionResetError', + 'NewConnectionError', + '10054', + '10061' + ] + return any(error in error_str for error in connection_errors) + +def restart_appium_server(port=4723): + """重启Appium服务器""" + try: + # 杀死可能存在的Appium进程 + subprocess.run(['taskkill', '/f', '/im', 'node.exe'], + capture_output=True, shell=True) + time.sleep(2) + + # 启动Appium服务器 + # appium_command = f'appium -p {port}' + appium_command = f"appium -p {port} --base-path /wd/hub" + subprocess.Popen(appium_command, shell=True) + + # 等待Appium启动 + time.sleep(10) + return True + except Exception as e: + print(f"重启Appium服务器失败: {str(e)}") + return False + +def is_appium_running(port=4723): + """检查Appium服务器是否在运行""" + try: + with socket.socket(socket.AF_INET, socket.SOCK_STREAM) as s: + result = s.connect_ex(('127.0.0.1', port)) + return result == 0 + except: + return False diff --git a/globals/ex_apis.py b/globals/ex_apis.py new file mode 100644 index 0000000..ff5632b --- /dev/null +++ b/globals/ex_apis.py @@ -0,0 +1,298 @@ +# external_apis.py +import requests +import json +import logging +import random +from typing import Dict, Tuple, Optional +import time + +class WeatherAPI: + def __init__(self): + self.logger = logging.getLogger(__name__) + # 使用腾讯天气API + self.base_url = "https://wis.qq.com/weather/common" + + def parse_city(self, city_string: str) -> Tuple[str, str, str]: + """ + 解析城市字符串,返回省份、城市、区县 + + 参数: + city_string: 完整的地址字符串 + + 返回: + (province, city, county) + """ + # 匹配省份或自治区 + province_regex = r"(.*?)(省|自治区)" + # 匹配城市或州 + city_regex = r"(.*?省|.*?自治区)(.*?市|.*?州)" + # 匹配区、县或镇 + county_regex = r"(.*?市|.*?州)(.*?)(区|县|镇)" + + province = "" + city = "" + county = "" + + import re + + # 先尝试匹配省份或自治区 + province_match = re.search(province_regex, city_string) + if province_match: + province = province_match.group(1).strip() + + # 然后尝试匹配城市或州 + city_match = re.search(city_regex, city_string) + if city_match: + city = city_match.group(2).strip() + else: + # 如果没有匹配到城市,则可能是直辖市或者直接是区/县 + city = city_string + + # 最后尝试匹配区、县或镇 + county_match = re.search(county_regex, city_string) + if county_match: + county = county_match.group(2).strip() + # 如果有区、县或镇,那么前面的城市部分需要重新解析 + if city_match: + city = city_match.group(2).strip() + + # 特殊情况处理,去除重复的省市名称 + if city and province and city.startswith(province): + city = city.replace(province, "").strip() + if county and city and county.startswith(city): + county = county.replace(city, "").strip() + + # 去除后缀 + city = city.rstrip('市州') + if county: + county = county.rstrip('区县镇') + + # self.logger.info(f"解析结果 - 省份: {province}, 城市: {city}, 区县: {county}") + return province, city, county + + def get_weather_by_qq_api(self, province: str, city: str, county: str) -> Optional[Dict]: + """ + 使用腾讯天气API获取天气信息 + + 参数: + province: 省份 + city: 城市 + county: 区县 + + 返回: + 天气信息字典 or None + """ + try: + params = { + 'source': 'pc', + 'weather_type': 'observe', + 'province': province, + 'city': city, + 'county': county + } + + response = requests.get(self.base_url, params=params, timeout=10) + response.raise_for_status() + + data = response.json() + + if data.get('status') == 200: + observe_data = data.get('data', {}).get('observe', {}) + + return { + 'weather': observe_data.get('weather', ''), + 'temperature': observe_data.get('degree', ''), + 'pressure': observe_data.get('pressure', '1013') + } + else: + self.logger.error(f"腾讯天气API错误: {data.get('message')}") + return None + + except Exception as e: + self.logger.error(f"腾讯天气API调用失败: {str(e)}") + return None + + def normalize_weather_text(self, weather_text: str) -> str: + """ + 将天气描述标准化为: 晴;阴;雨;雪;风;其他 + + 参数: + weather_text: 原始天气描述 + + 返回: + 标准化后的天气文本 + """ + if not weather_text: + return '其他' + + weather_text_lower = weather_text.lower() + + # 晴 + if any(word in weather_text_lower for word in ['晴', 'sunny', 'clear']): + return '晴' + # 阴 + elif any(word in weather_text_lower for word in ['阴', '多云', 'cloudy', 'overcast']): + return '阴' + # 雨 + elif any(word in weather_text_lower for word in ['雨', 'rain', 'drizzle', 'shower']): + return '雨' + # 雪 + elif any(word in weather_text_lower for word in ['雪', 'snow']): + return '雪' + # 风 + elif any(word in weather_text_lower for word in ['风', 'wind']): + return '风' + # 其他 + else: + return '其他' + + def adjust_pressure(self, pressure: float) -> float: + """ + 调整气压值:低于700时,填700-750之间随机一个;高于700就按实际情况填 + + 参数: + pressure: 原始气压值 + + 返回: + 调整后的气压值 + """ + try: + pressure_float = float(pressure) + if pressure_float < 700: + adjusted_pressure = random.randint(700, 750) + self.logger.info(f"气压值 {pressure_float} 低于700,调整为: {adjusted_pressure:.1f}") + return round(adjusted_pressure, 1) + else: + self.logger.info(f"使用实际气压值: {pressure_float}") + return round(pressure_float, 1) + except (ValueError, TypeError): + self.logger.warning(f"气压值格式错误: {pressure},使用默认值720") + return round(random.randint(700, 750), 1) + + def get_weather_by_address(self, address: str, max_retries: int = 2) -> Optional[Dict]: + """ + 根据地址获取天气信息 + + 参数: + address: 地址字符串 + max_retries: 最大重试次数 + + 返回: + { + 'weather': '晴/阴/雨/雪/风/其他', + 'temperature': 温度值, + 'pressure': 气压值 + } or None + """ + # self.logger.info(f"开始获取地址 '{address}' 的天气信息") + + # 首先解析地址 + province, city, county = self.parse_city(address) + + if not province and not city: + self.logger.error("无法解析地址") + return self.get_fallback_weather() + + # 获取天气信息 + weather_data = None + for attempt in range(max_retries): + try: + # self.logger.info(f"尝试获取天气信息 (第{attempt + 1}次)") + weather_data = self.get_weather_by_qq_api(province, city, county) + if weather_data: + break + time.sleep(1) # 短暂延迟后重试 + except Exception as e: + self.logger.warning(f"第{attempt + 1}次尝试失败: {str(e)}") + time.sleep(1) + + if not weather_data: + self.logger.warning("获取天气信息失败,使用备用数据") + return self.get_fallback_weather() + + # 处理天气数据 + try: + # 标准化天气文本 + normalized_weather = self.normalize_weather_text(weather_data['weather']) + + # 调整气压值 + adjusted_pressure = self.adjust_pressure(weather_data['pressure']) + + # 处理温度 + temperature = float(weather_data['temperature']) + + result = { + 'weather': normalized_weather, + 'temperature': round(temperature, 1), + 'pressure': adjusted_pressure + } + + self.logger.info(f"成功获取天气信息: {result}") + return result + + except Exception as e: + self.logger.error(f"处理天气数据时出错: {str(e)}") + return self.get_fallback_weather() + + def get_fallback_weather(self) -> Dict: + """ + 获取备用天气数据(当所有API都失败时使用) + + 返回: + 默认天气数据 + """ + self.logger.info("使用备用天气数据") + return { + 'weather': '阴', + 'temperature': round(random.randint(15, 30), 1), + 'pressure': round(random.randint(700, 750), 1) + } + + def get_weather_simple(self, address: str) -> Tuple[str, float, float]: + """ + 简化接口:直接返回天气、温度、气压 + + 参数: + address: 地址字符串 + + 返回: + (weather, temperature, pressure) + """ + weather_data = self.get_weather_by_address(address) + if weather_data: + return weather_data['weather'], weather_data['temperature'], weather_data['pressure'] + else: + fallback = self.get_fallback_weather() + return fallback['weather'], fallback['temperature'], fallback['pressure'] + +# 创建全局实例 +weather_api = WeatherAPI() + +# 直接可用的函数 +def get_weather_by_address(address: str) -> Optional[Dict]: + """ + 根据地址获取天气信息(直接调用函数) + + 参数: + address: 地址字符串 + + 返回: + { + 'weather': '晴/阴/雨/雪/风/其他', + 'temperature': 温度值, + 'pressure': 气压值 + } or None + """ + return weather_api.get_weather_by_address(address) + +def get_weather_simple(address: str) -> Tuple[str, float, float]: + """ + 简化接口:直接返回天气、温度、气压 + + 参数: + address: 地址字符串 + + 返回: + (weather, temperature, pressure) + """ + return weather_api.get_weather_simple(address) diff --git a/globals/global_variable.py b/globals/global_variable.py new file mode 100644 index 0000000..86549ce --- /dev/null +++ b/globals/global_variable.py @@ -0,0 +1,17 @@ +# 全局变量 +GLOBAL_DEVICE_ID = "" # 设备ID +GLOBAL_USERNAME = "czyuzongwen" # 用户名 +GLOBAL_CURRENT_PROJECT_NAME = "" # 当前测试项目名称 +GLOBAL_LINE_NUM = "" # 线路编码 +GLOBAL_BREAKPOINT_STATUS_CODES = [0,3] # 要获取的断点状态码列表 +GLOBAL_UPLOAD_BREAKPOINT_LIST = [] +GLOBAL_UPLOAD_BREAKPOINT_DICT = {} +GLOBAL_TESTED_BREAKPOINT_LIST = [] # 测量结束的断点列表 +LINE_TIME_MAPPING_DICT = {} # 存储所有线路编码和对应的时间的全局字典 +GLOBAL_BREAKPOINT_DICT = {} # 存储测量结束的断点名称和对应的线路编码的全局字典 +GLOBAL_NAME_TO_ID_MAP = {} # 存储所有数据员姓名和对应的身份证号的全局字典 +GLOBAL_UPLOAD_SUCCESS_BREAKPOINT_LIST = [] # 上传成功的`断点列表 + + + + diff --git a/globals/ids.py b/globals/ids.py new file mode 100644 index 0000000..33cbb60 --- /dev/null +++ b/globals/ids.py @@ -0,0 +1,59 @@ +# ids.py +# 登录界面 +LOGIN_USERNAME = "com.bjjw.cjgc:id/et_user_name" +LOGIN_PASSWORD = "com.bjjw.cjgc:id/et_user_psw" +LOGIN_BTN = "com.bjjw.cjgc:id/btn_login" + +# 更新相关 +UPDATE_WORK_BASE = "com.bjjw.cjgc:id/btn_update_basepoint" +UPDATE_LEVEL_LINE = "com.bjjw.cjgc:id/btn_update_line" +UPDATE_LEVEL_LINE_CONFIRM = "com.bjjw.cjgc:id/commit" + +# 弹窗 & 加载 +ALERT_DIALOG = "android:id/content" +LOADING_DIALOG = "android:id/custom" + +# 底部导航栏 +DOWNLOAD_TABBAR_ID = "com.bjjw.cjgc:id/img_1_layout" +MEASURE_TABBAR_ID = "com.bjjw.cjgc:id/img_3_layout" + +# 测量相关 +MEASURE_BTN_ID = "com.bjjw.cjgc:id/select_point_update_tip_tv" +MEASURE_LIST_ID = "com.bjjw.cjgc:id/line_list" +MEASURE_LISTVIEW_ID = "com.bjjw.cjgc:id/itemContainer" +MEASURE_NAME_TEXT_ID = "com.bjjw.cjgc:id/title" +MEASURE_NAME_ID = "com.bjjw.cjgc:id/sectName" +MEASURE_BACK_ID = "com.bjjw.cjgc:id/btn_back" +MEASURE_BACK_ID_2 = "com.bjjw.cjgc:id/stop_measure_btn" + +# 天气、观测类型 +MEASURE_TITLE_ID = "com.bjjw.cjgc:id/title_bar" +MEASURE_WEATHER_ID = "com.bjjw.cjgc:id/point_list_weather_sp" +MEASURE_TYPE_ID = "com.bjjw.cjgc:id/point_list_mtype_sp" +MEASURE_SELECT_ID = "android:id/select_dialog_listview" +SELECT_DIALOG_TEXT1_ID = "android:id/text1" +MEASURE_PRESSURE_ID = "com.bjjw.cjgc:id/point_list_barometric_et" +MEASURE_TEMPERATURE_ID = "com.bjjw.cjgc:id/point_list_temperature_et" +MEASURE_SAVE_ID = "com.bjjw.cjgc:id/select_point_order_save_btn" + +# 日期选择器 +DATE_START = "com.bjjw.cjgc:id/date" +DATE_END = "com.bjjw.cjgc:id/date_end" +SCRCOLL_YEAR = "com.bjjw.cjgc:id/wheelView1" +SCRCOLL_MONTH = "com.bjjw.cjgc:id/wheelView2" +SCRCOLL_DAY = "com.bjjw.cjgc:id/wheelView3" +SCRCOLL_CONFIRM = "com.bjjw.cjgc:id/okBtn" +SCRCOLL_CANCEL = "com.bjjw.cjgc:id/cancelBtn" + +# 其他 +CONNECT_LEVEL_METER = "com.bjjw.cjgc:id/point_conn_level_btn" +PINGCHAR_PROCESS = "com.bjjw.cjgc:id/point_measure_btn" +LEVEL_METER_MANAGER = "com.bjjw.cjgc:id/btn_back" +LEVEL_METER_MANAGER_LIST = "com.bjjw.cjgc:id/total_station_expandlist" +START_MEASURE = "com.bjjw.cjgc:id/btn_control_begin_or_end" +ALTER_MEASURE = "com.bjjw.cjgc:id/order_title" +MATCH_VISIABLE = "com.bjjw.cjgc:id/title_paired_devices" +BLUETOOTH_PAIR = "com.bjjw.cjgc:id/paired_devices" +REPID_MEASURE = "com.bjjw.cjgc:id/measure_remeasure_all_btn" +ONE = "com.bjjw.cjgc:id/auto_measure_all_station_text" + diff --git a/main.py b/main.py new file mode 100644 index 0000000..803a05b --- /dev/null +++ b/main.py @@ -0,0 +1,478 @@ +# actions.py 主自动化脚本 +import os +import logging +import time +import subprocess +from appium import webdriver +from appium.options.android import UiAutomator2Options +from appium.webdriver.common.appiumby import AppiumBy +from selenium.webdriver.support.ui import WebDriverWait +from selenium.webdriver.support import expected_conditions as EC +from selenium.common.exceptions import TimeoutException, NoSuchElementException + +import globals.ids as ids +import globals.global_variable as global_variable # 导入全局变量模块 +import permissions # 导入权限处理模块 +import globals.apis as apis +from globals.driver_utils import init_appium_driver, ensure_appium_server_running, safe_quit_driver, is_app_launched, launch_app_manually +from page_objects.login_page import LoginPage +from page_objects.download_tabbar_page import DownloadTabbarPage +from page_objects.screenshot_page import ScreenshotPage +from page_objects.upload_config_page import UploadConfigPage +from page_objects.more_download_page import MoreDownloadPage + + +# 配置日志 +logging.basicConfig( + level=logging.INFO, + format="%(asctime)s - %(levelname)s: %(message)s", + handlers=[ + logging.FileHandler("appium_automation.log"), + logging.StreamHandler() + ] +) + +class DeviceAutomation: + @staticmethod + def get_device_id() -> str: + + # """ + # 获取设备ID,优先使用已连接设备,否则使用全局配置 + # """ + # try: + # # 检查已连接设备 + # result = subprocess.run( + # ["adb", "devices"], + # capture_output=True, + # text=True, + # timeout=10 + # ) + + # # 解析设备列表 + # for line in result.stdout.strip().split('\n')[1:]: + # if line.strip() and "device" in line and "offline" not in line: + # device_id = line.split('\t')[0] + # logging.info(f"使用已连接设备: {device_id}") + # global_variable.GLOBAL_DEVICE_ID = device_id + # return device_id + + # except Exception as e: + # logging.warning(f"设备检测失败: {e}") + + """ + 获取设备ID,优先使用无线连接设备,否则尝试开启无线调试,最后使用全局配置 + """ + try: + # 检查已连接设备 + result = subprocess.run( + ["adb", "devices"], + capture_output=True, + text=True, + timeout=10 + ) + + # 解析设备列表 + wireless_device_id = None + usb_device_id = None + + # 先查找无线连接的设备(IP:端口格式) + for line in result.stdout.strip().split('\n')[1:]: + if line.strip() and "device" in line and "offline" not in line: + current_device = line.split('\t')[0] + # 检查是否为IP:端口格式的无线连接 + if ":" in current_device and any(char.isdigit() for char in current_device): + wireless_device_id = current_device + logging.info(f"使用无线连接设备: {wireless_device_id}") + global_variable.GLOBAL_DEVICE_ID = wireless_device_id + return wireless_device_id + else: + # 记录第一个USB连接的设备 + if not usb_device_id: + usb_device_id = current_device + + # 如果没有找到无线连接的设备,尝试使用USB设备开启无线调试 + if not wireless_device_id and usb_device_id: + logging.info(f"未找到无线连接设备,尝试使用USB设备 {usb_device_id} 开启无线调试") + + # 尝试获取设备IP地址 + try: + import re + import time + + ip_result = subprocess.run( + ["adb", "-s", usb_device_id, "shell", "ip", "-f", "inet", "addr", "show", "wlan0"], + capture_output=True, + text=True, + timeout=10 + ) + + # 解析IP地址 + ip_output = ip_result.stdout + if "inet " in ip_output: + # 提取IP地址 + ip_match = re.search(r'inet\s+(\d+\.\d+\.\d+\.\d+)', ip_output) + if ip_match: + device_ip = ip_match.group(1) + logging.info(f"获取到设备IP地址: {device_ip}") + + # 开启无线调试 + tcpip_result = subprocess.run( + ["adb", "-s", usb_device_id, "tcpip", "5555"], + capture_output=True, + text=True, + timeout=10 + ) + + if "restarting in TCP mode port: 5555" in tcpip_result.stdout: + logging.info("无线调试已开启,端口: 5555") + + # 等待几秒钟让设备准备好 + time.sleep(3) + + # 连接到无线设备 + connect_result = subprocess.run( + ["adb", "connect", f"{device_ip}:5555"], + capture_output=True, + text=True, + timeout=10 + ) + + if "connected to" in connect_result.stdout: + logging.info(f"成功连接到无线设备: {device_ip}:5555") + global_variable.GLOBAL_DEVICE_ID = f"{device_ip}:5555" + return f"{device_ip}:5555" + else: + logging.warning(f"连接无线设备失败: {connect_result.stderr}") + logging.info(f"使用USB设备: {usb_device_id}") + global_variable.GLOBAL_DEVICE_ID = usb_device_id + return usb_device_id + else: + logging.warning(f"开启无线调试失败: {tcpip_result.stderr}") + logging.info(f"使用USB设备: {usb_device_id}") + global_variable.GLOBAL_DEVICE_ID = usb_device_id + return usb_device_id + else: + logging.warning("未找到设备IP地址") + logging.info(f"使用USB设备: {usb_device_id}") + global_variable.GLOBAL_DEVICE_ID = usb_device_id + return usb_device_id + else: + logging.warning("无法获取设备IP地址,可能设备未连接到WiFi") + logging.info(f"使用USB设备: {usb_device_id}") + global_variable.GLOBAL_DEVICE_ID = usb_device_id + return usb_device_id + except Exception as e: + logging.warning(f"开启无线调试时出错: {str(e)}") + logging.info(f"使用USB设备: {usb_device_id}") + global_variable.GLOBAL_DEVICE_ID = usb_device_id + return usb_device_id + + except Exception as e: + logging.warning(f"设备检测失败: {e}") + + + # 使用全局配置 + device_id = global_variable.GLOBAL_DEVICE_ID + logging.info(f"使用全局配置设备: {device_id}") + return device_id + + def __init__(self, device_id=None): + # 如果没有提供设备ID,则自动获取 + if device_id is None: + self.device_id = self.get_device_id() + else: + self.device_id = device_id + + # 初始化权限 + if permissions.grant_appium_permissions(self.device_id): + logging.info(f"设备 {self.device_id} 权限授予成功") + else: + logging.warning(f"设备 {self.device_id} 权限授予失败") + + # 确保Appium服务器正在运行 + ensure_appium_server_running(4723) + + # 初始化驱动 + self.init_driver() + # 先拼接,后创建测试结果目录 + self.results_dir = os.path.join(os.path.dirname(os.path.abspath(__file__)), 'test_results') + os.makedirs(self.results_dir, exist_ok=True) + + + def init_driver(self): + """初始化Appium驱动""" + try: + # 使用全局函数初始化驱动 + self.driver, self.wait = init_appium_driver(self.device_id) + # 初始化页面对象 + logging.info(f"设备 {self.device_id} 开始初始化页面对象") + self.login_page = LoginPage(self.driver, self.wait) + self.download_tabbar_page = DownloadTabbarPage(self.driver, self.wait, self.device_id) + self.screenshot_page = ScreenshotPage(self.driver, self.wait, self.device_id) + self.upload_config_page = UploadConfigPage(self.driver, self.wait, self.device_id) + self.more_download_page = MoreDownloadPage(self.driver, self.wait,self.device_id) + logging.info(f"设备 {self.device_id} 所有页面对象初始化完成") + + # 检查应用是否成功启动 + if is_app_launched(self.driver): + logging.info(f"设备 {self.device_id} 沉降观测App已成功启动") + else: + logging.warning(f"设备 {self.device_id} 应用可能未正确启动") + # 手动启动应用 + launch_app_manually(self.driver, self.device_id) + + except Exception as e: + logging.error(f"设备 {self.device_id} 初始化驱动失败: {str(e)}") + raise + + def is_app_launched(self): + """检查应用是否已启动""" + try: + return is_app_launched(self.driver) + except Exception as e: + logging.error(f"设备 {self.device_id} 检查应用启动状态时出错: {str(e)}") + return False + + def is_element_present(self, by, value): + """检查元素是否存在""" + try: + self.driver.find_element(by, value) + return True + except NoSuchElementException: + return False + + def handle_app_state(self): + """根据当前应用状态处理相应的操作""" + try: + login_btn_exists = self.login_page.is_login_page() + if not login_btn_exists: + logging.error(f"设备 {self.device_id} 未知应用状态,无法确定当前页面,跳转到登录页面") + if self.navigate_to_login_page(self.driver, self.device_id): + logging.info(f"设备 {self.device_id} 成功跳转到登录页面") + return self.handle_app_state() # 递归调用处理登录后的状态 + else: + logging.error(f"设备 {self.device_id} 跳转到登录页面失败") + return False + + # 处理登录页面状态 + logging.info(f"设备 {self.device_id} 检测到登录页面,执行登录操作") + max_retries = 1 + login_success = False + + for attempt in range(max_retries + 1): + if self.login_page.login(): + login_success = True + break + else: + if attempt < max_retries: + logging.warning(f"设备 {self.device_id} 登录失败,准备重试 ({attempt + 1}/{max_retries})") + time.sleep(2) # 等待2秒后重试 + else: + logging.error(f"设备 {self.device_id} 登录失败,已达到最大重试次数") + + if not login_success: + return False + + logging.info(f"设备 {self.device_id} 登录成功,继续执行更新操作") + time.sleep(1) + + # 执行更新操作 + if not self.download_tabbar_page.download_tabbar_page_manager(): + logging.error(f"设备 {self.device_id} 更新操作执行失败") + return False + + # 获取状态为3的线路。 + apis.get_line_info_and_save_global(user_name=global_variable.GLOBAL_USERNAME); + # # 虚拟数据替代 + # global_variable.GLOBAL_UPLOAD_BREAKPOINT_DICT = {'CDWZQ-2标-龙骨湾右线大桥-0-7号墩-平原': 'L156372', 'CDWZQ-2标-蓝家湾特大 桥-31-31-平原': 'L159206'} + # global_variable.GLOBAL_UPLOAD_BREAKPOINT_LIST = list(global_variable.GLOBAL_UPLOAD_BREAKPOINT_DICT.keys()) + + # 点击测量导航栏按钮 + measure_page_btn = WebDriverWait(self.driver, 5).until( + EC.element_to_be_clickable((AppiumBy.ID, "com.bjjw.cjgc:id/img_3_layout")) + ) + measure_page_btn.click() + + + # 处理平差 + self.screenshot_page.screenshot_page_manager(self.device_id) + + + # 检查是否有需要上传的断点 + if not global_variable.GLOBAL_UPLOAD_BREAKPOINT_LIST: + logging.info(f"设备 {self.device_id} 断点列表为空,无需执行上传操作") + return False + + # 点击上传导航栏按钮 + upload_page_btn = WebDriverWait(self.driver, 5).until( + EC.element_to_be_clickable((AppiumBy.ID, "com.bjjw.cjgc:id/img_2_layout")) + ) + upload_page_btn.click() + + + # 遍历断点列表,逐个执行上传操作 + upload_success_count = 0 + for breakpoint_name in global_variable.GLOBAL_UPLOAD_BREAKPOINT_LIST: + try: + logging.info(f"设备 {self.device_id} 开始处理断点 '{breakpoint_name}' 的上传") + + # 安全地获取断点信息 + line_num = global_variable.GLOBAL_UPLOAD_BREAKPOINT_DICT.get(breakpoint_name) + if line_num is None: + logging.warning(f"设备 {self.device_id} 断点 '{breakpoint_name}' 在字典中未找到,跳过上传") + continue + if not line_num: + logging.warning(f"设备 {self.device_id} 断点 '{breakpoint_name}' 未获取到line_num,跳过上传") + continue + + # 执行上传配置管理,传入当前断点名称 + if self.upload_config_page.upload_config_page_manager(self.results_dir, breakpoint_name, line_num): + logging.info(f"设备 {self.device_id} 断点 '{breakpoint_name}' 上传成功") + upload_success_count += 1 + else: + logging.error(f"设备 {self.device_id} 断点 '{breakpoint_name}' 上传失败") + + except Exception as e: + logging.error(f"设备 {self.device_id} 处理断点 '{breakpoint_name}' 时发生异常: {str(e)}") + + logging.info(f"设备 {self.device_id} 上传配置管理执行完成,成功上传 {upload_success_count}/{len(global_variable.GLOBAL_UPLOAD_BREAKPOINT_LIST)} 个断点") + + # 如果所有断点都上传成功,返回True;否则返回False + all_upload_success = upload_success_count == len(global_variable.GLOBAL_UPLOAD_BREAKPOINT_LIST) + if all_upload_success: + logging.info(f"设备 {self.device_id} 所有断点上传成功") + # 把上传成功的断点写入日志文件"上传成功的断点.txt" + with open(os.path.join(self.results_dir, "上传成功的断点.txt"), "w", encoding='utf-8') as f: + for bp in global_variable.GLOBAL_UPLOAD_SUCCESS_BREAKPOINT_LIST: + f.write(f"{bp}\n") + else: + logging.warning(f"设备 {self.device_id} 部分断点上传失败") + # 把上传成功的断点写入日志文件"上传成功的断点.txt" + with open(os.path.join(self.results_dir, "上传成功的断点.txt"), "w", encoding='utf-8') as f: + for bp in global_variable.GLOBAL_UPLOAD_SUCCESS_BREAKPOINT_LIST: + f.write(f"{bp}\n") + # 把上传失败的断点写入日志文件"上传失败的断点.txt" + with open(os.path.join(self.results_dir, "上传失败的断点.txt"), "w", encoding='utf-8') as f: + for bp in set(global_variable.GLOBAL_UPLOAD_BREAKPOINT_LIST)-set(global_variable.GLOBAL_UPLOAD_SUCCESS_BREAKPOINT_LIST): + f.write(f"{bp}\n") + + return all_upload_success + + except Exception as e: + logging.error(f"设备 {self.device_id} 处理应用状态时出错: {str(e)}") + return False + + + + def check_and_click_confirm_popup_appium(self): + """ + 适用于Appium的弹窗检测函数 + + Returns: + bool: 是否成功处理弹窗 + """ + try: + from appium.webdriver.common.appiumby import AppiumBy + + # 使用self.driver而不是参数 + if not hasattr(self, 'driver') or self.driver is None: + logging.warning("driver未初始化,无法检测弹窗") + return False + + # 检查弹窗消息 + message_elements = self.driver.find_elements(AppiumBy.XPATH, '//android.widget.TextView[@text="是否退出测量界面?"]') + + if message_elements: + logging.info("检测到退出测量界面弹窗") + + # 点击"是"按钮 + confirm_buttons = self.driver.find_elements(AppiumBy.XPATH, '//android.widget.Button[@text="是" and @resource-id="android:id/button1"]') + if confirm_buttons: + confirm_buttons[0].click() + logging.info("已点击'是'按钮") + time.sleep(1) + return True + else: + logging.warning("未找到'是'按钮") + return False + else: + return False + + except Exception as e: + logging.error(f"Appium检测弹窗时发生错误: {str(e)}") + return False + + + + def navigate_to_login_page(self, driver, device_id): + """ + 补充的跳转页面函数:当设备处于未知状态时,尝试跳转到登录页面 + + 参数: + driver: 已初始化的Appium WebDriver对象 + device_id: 设备ID,用于日志记录 + """ + try: + target_package = 'com.bjjw.cjgc' + target_activity = '.activity.LoginActivity' + # 使用ADB命令启动Activity + try: + logging.info(f"尝试使用ADB命令启动LoginActivity: {target_package}/{target_activity}") + adb_command = f"adb -s {device_id} shell am start -n {target_package}/{target_activity}" + result = subprocess.run(adb_command, shell=True, capture_output=True, text=True) + if result.returncode == 0: + logging.info(f"使用ADB命令启动LoginActivity成功") + time.sleep(2) # 等待Activity启动 + return True + else: + logging.warning(f"ADB命令执行失败: {result.stderr}") + except Exception as adb_error: + logging.warning(f"执行ADB命令时出错: {adb_error}") + except Exception as e: + logging.error(f"跳转到登录页面过程中发生未预期错误: {e}") + + # 所有尝试都失败 + return False + + + + + def run_automation(self): + """运行自动化流程""" + try: + success = self.handle_app_state() + # success = self.test_handle_app_state() + if success: + logging.info(f"设备 {self.device_id} 自动化流程执行成功") + else: + logging.error(f"设备 {self.device_id} 自动化流程执行失败") + return success + except Exception as e: + logging.error(f"设备 {self.device_id} 自动化执行过程中发生错误: {str(e)}") + return False + finally: + self.quit() + + def quit(self): + """关闭驱动""" + safe_quit_driver(getattr(self, 'driver', None), self.device_id) + +# 主执行逻辑 +if __name__ == "__main__": + + # 单个设备配置 - 现在DeviceAutomation会自动获取设备ID + + try: + automation = DeviceAutomation() + success = automation.run_automation() + + if success: + logging.info(f"设备自动化流程执行成功") + else: + logging.error(f"设备自动化流程执行失败") + except Exception as e: + logging.error(f"设备执行出错: {str(e)}") + + + + \ No newline at end of file diff --git a/music/901135.wav b/music/901135.wav new file mode 100644 index 0000000000000000000000000000000000000000..1464ac28298ee9ba1207d4b2f4fbb019e4c0b8f5 GIT binary patch literal 209452 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 literal 0 HcmV?d00001 diff --git a/page_objects/__init__.py b/page_objects/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/page_objects/__pycache__/__init__.cpython-312.pyc b/page_objects/__pycache__/__init__.cpython-312.pyc new file mode 100644 index 0000000000000000000000000000000000000000..b3050e15623923b179ca37b202870909a192144d GIT binary patch literal 145 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 literal 0 HcmV?d00001 diff --git a/page_objects/__pycache__/screenshot_page.cpython-312.pyc b/page_objects/__pycache__/screenshot_page.cpython-312.pyc new file mode 100644 index 0000000000000000000000000000000000000000..76bb38a27cb19b80435ef7224e2369caea26ee92 GIT binary patch literal 68095 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�B9hd9DLpMkfOuHi-rhdq!yc)(&O5aqLM_>qY=af9Q=}o9nOTsE5(fS zBeG>gZsD+1CD^1SqC$tv0ivUTc;n;*3n0vH9yJ{TAzl31{&A54h8k5 zZ&-}>4era4Lu>@-$$!ckV%Y9$zstFNOqj;(Lv+rb7Z!N zFaXDA`)=Iz0||LTqE9~eK12hj0!fjcHuwQl^=(pG{~adO@fakbc{+H2y$#ghMMNWC zYAg)q>k3WV9o3K42#uyjn}X_1L`X~g9f(ptxXUJ$^*^w)c~Z{_<_@>_gY=f;2Lmk& z9>_Bi`+-o^gXROdNE2+>4FH`Agvq4lKtGM*m&!4Nu(k7>KY{C1NZvrl?N<)M-x8HR zEzwMfS)&k2UA!fFH5}g!A;-~}4#;utpzbJn5A$vH&ywbQr~M#5 zMcQ2n@E?K*C&{$>qq@U7HhGy#(|Av@9OIV-^M`EZS~g>yOZT`)H-AG)AzvN4NB*Ts zRRo&%yn8xXLGf^tC$H*DSy*EJ3C%r)Iwc>Z%szGf$Gbo6`KX88xMQNM^#o|&%iLMj z&a7%z*1|80`LH#FanZeSsQRT|W+)gfcNfd*ySlK1>1&< zZ0b9M3iYw@aqZaC?9z6&oN=X_J-J2q^^XL1~fhG)cSUiGiL+p?AgsY8#P-PQs9qbdk*&>TOq0^br`Db`6)c7+DQlAn! z=GZpZ_9xi3ofF&J*=-D)iJP>Ev;ZH&r+2(2dgWZ%M+K29%48qStZvLwTrI6pz{#~4 zH4%+76d%v8DTcFO=hbK%m&iUX&w`Wd(KDdV^(9%5ewL+eELDG2q=f6AmB=w)O7b(b zjSJPE%_I2*a?CFz`6Wdwioviz4&{Gil4HIYum7e@yRu68n>h+-^EdM}kpC^%R?JZT zwpa~Uemg^rSE`hlpQ~L}rTm?&c-0K$?`EhW?`NPOh+*ylkb5*SjF>z2g$f3mPJ#;l zud$!o15B-b`Ul_$B=r}9++8Xf8W=+J95QNDV6_AulA(^nU~33zwLh3!NZ%3r7=syx zWc__87l=i(45>EA-B96@rRe<#AHVk(%}t~*H>lf@7%%5Nesp@x4&e&}rxng6XI2#R z?mumPO7OxDSXC1H4L;H+!~w2jLaZw(jtL_YNX)appt3JEM2k{9kPtK)VhF|ta8q>O zu!ns!@xIkW;`tf*ibNQThA8|6(Ql{JC=*2fV1kjp{}@3SOU+GUgUn5mh%lDZ6p`)1 z&=B}Xr^MtUEkbrs=JY+Hf>gakf(-X3iPSfH+pJ)6ySA;z+|{2by50u+1KOJ{_AYaO zoai#A)}Z?m^M=8+ZP;WHcB;PnWAMy!An{jfoch(!)VCqlo&(|Roi~3rdF*>+zh%mj zfE(BCmrhcx70}@@7(#&45n&Ng@5RIXM%*7x`$Tp+f;vX7QD1XtcS}UrNW%Nw<`hzn z80Z`w0s62L_w;7R0=P)khu|4Eb?Q9orjA^^`^*cVSAsp7rcS*Y+#c$o-ultqlh0He z8yv;HR-b)l@~w;F1|Scep_^XQCldp0GN{x&OGZgcxVdLzlo0@D>(THYvn!Yj)n`ph=5fv*jlq#OigAhI|HqK)Zv z*zClNmpT@5t@oEp^A4fPX8;DV{TuKsDDp23U=!GkMK0Z9Fqh3N;k0S;iHvG@#$(Qm z$Hdw+*ru6Xw1J(maYDa|*P%to(oF8u+0NA2@9che&)a+0+LnpbtwSpb;XK!wKKHT) zw})oyw@jqB46XK4U$I$0Te_=PIICB%^H)Ng-;M3=jk}#2ce5KTY(}R`w+EDPdc%nY z?z9qTTFK`zC2Ylt>)r0AC!9@Bd|vT{pQepQcSNmSsXJqyGXp8|w2NtML(4?Q*5L?G ze8%x5$Cj`K^FEKC=Sj$PCvfIB0?mr&Z2odrLLN-)lNh8<>eo)yttBWY-1PfoG`S*Ue$?7@Z7|d<13GWc?)f4jw@j<*x>j%t%FNG z%k;Pzo0GAz?RK^RO^L~V&k1xBJrWZ-Z>>wePEh$Nv_8Ld5`sDdci{ASt@{{0v^Zvs zp*PXP5B1Qp=;cYWPh*QPtxkjVdQx8FY~}TZ8c07=6*g8VKdVqf-cK_qsAz7%3VJk> z)Q9G!h)|!-Ps(#^di6)J*$hGAC?g0sqzFebsSL>NVQW#rw4eWCp9&DKfYt$BQnBr$ zwdiN;0th(F&x@>zXhWr1lrW0nR9&Gq7#1AQQ8-OE7VZlR+F)4g_Dk@Z)#*T!>XG7!?^mr=SYqmPfh?vN2da0qU%O;B4 zwhBf>C7D%_GX^(=DDU{l-BSl?H2=xr@87=kCT)stdC=gPi!}wwnRpWL*+(zjy7W^H zc^y7`=gbk>1Z*gA_vf!Sa{@IwmBf9BP9GvMKj~K!>~xI!xzM<)5bqXY;NGb}D$ zrw<7Iy2l35X)UaR-~(>e4+SisFGfh(k-mmTF~JwMDfvlvihIFkMhw zTdcfRtcJX_!a5aZb1 zGazjIZp#hPKEnKf10V~Ut#NxDijCL$!l0GNfZ}k$Ust>dZWdla$ueUXn)PuN)u*uhA^N&?0p z%C%7YS%!YoQFg(*-7mCQAVBsY2GYDX+HiJPwpw+x0su3CQo5*csbw+P| zp&!Gxw}dGP~^3-McN zq?V0nX-PKk@!Op|m%9$p!6XdX_yDewq=JD&W=;9VHV5A0p{4TrDA`rj3{0yqy_Q!2 z>Bos_m{wx?NtCuBLitHjaf4ddT$$MG!21k5L}(fjC@0Gnmy+^EVB{po zioy?pqWoK`z65c=h`>A4l0XlCRlodLoj#=~EQw(4<{P2da{m!VKxe?8gwOy&6to2d zA)>ThYEC4`GHy<_zAZ+5q(P9iO9U}ARWjsPgb*VgZ^Fm~q`?7nB1sECiY+QNkS+vcW9kamOr(%TZWA^Uc|qq<0v~9tQHTWy_e9vG5*UM3gV%_q z!8tq?>;@$Gs0{N{=62X>joQ`vKsZF7H1DLn&-z>uo){|x>X^=QSs1odVOs!jPX##(kj_6Ya${2Xh{{MSF5zO zmCCD&ifc=i*Gkopmj;!;fKT;NxvgM8IK4cI@eir@V9*Bx3OuCvAP#(}{6I1)RU~+t z0N@(fJe*JS1V1y}K74`{SL5UUvY>?26C})oxiRZu8s!GW zL;xypjXyti=@^v>VH6q&|MT8Ux8JycP?S0+z?I?e-x@y%jEN04rtdLZ;oju4FVLEB zW9s}Fp|k{JHXCxjIBq7NJp0)fV7|D<2r9#uz;AU*`-H+=nIi{_ z2Xs%|Hy#U>k#a=xCtW&N(x($KCIaU1CfeGqyPfngqFqC6Q0%z+E8&I#z2kJ1ImAZ zNUav0ibyRdK;y)tm2GSWiKq)kYdrpepe+V>e3>)8Y}DccRgKh6hL5v3bn}LEz6cDVGdT+=h0@iWG> zuJ~%9tQZgbuqbTwDVA>XBV5mBG`n;g9*8(wT~B~=$UO1HF1EFU&D`zMSwx0T_^lkO z$*W6|eNXs3(wJZhkOAMBm|`I zH3+%RY#}RTIC2O4n}k;52O=2QMI;rUko1};U?_x`DFP2CX;Lp&Xi~^Lj2FY)jQf$OjS`r$3o9ZQ>ml zc>j9qdoXX`BzE_L-3v(5DNbJoajaqMcq^{Dj#p0Nad(O3J^(C}3PRC!nb97lk+D|7 zeQ!7fZE1&d*bc*CB~nbQ97G(;jH%dV4 z%R$C{yzd8nBh6>GoY``=^-QZPA2wh|L!`!IjmOsJnK$?`@)!}41*!JD*?k?8U?K1Ryk(GPXRE%|t z=d%fg!f1Vz@tAqMX1wiU?f7i=u}*eRKl}6ncHjWJ2V)fMq2U;66m=MtA-7)fuMm}? zL2++B&fUF*0vVm<)Mbqnj+#c(*s7-M{jl?Ti-R@pbLsZO8~V2|8|Cl{{_V>=7~h}3 zp}P zfoX}hu}FEns<<&%`B|c@97RSp%!CsyCBq(GPx4V{*OFPXa*_jQ$nc3J=b4+gRyc1sXkB!)nsRh zM{5zxAcT>GiRABsmI9~*ftn^pP2s4JKJqq0ennJ51T`EU{~0JL?_79^#!N;JPbyt` z`66u6)J)MXcF_pvhYFvOLA)YTXf@w^Ji!XURnf@5$A(=_#nou zqYouy0!qtY9Gx6{zX77g&WBY3yB`tVqnN9wsD(g%0T%?qfAQAbkqG}$2w*Wom^&`o ztd#oET8@A?P(&Tv0g+gt1SZ7x8<>WJvRldNC$>%`o4%BVYun{R%kC+{qT1!~NxI{U zo$|pbDyYv=52ukHkQTHOTy?b#oZPd}o z!;x-Hic^zvV%bRJ$O5)}1)I9krCH^PN%+%!tt=q}oR{#SJo_#JJk0pqE9)*U7%yX& zZFX(h#%|v+v89#W`~;iR=GO0Y>UW~&5!C(0hNRVSY|RqIPpTpLXo+k*n3t4a6MK zSjz`AejAEF9D-IsvJ#?45?GV#k>9ojJq01^OMeqMBEPs318PQ3cz=kTazqXITFpf4 zse>=zkJ6)ifFumLnSg_d=Vt`Wd=<7w>o0Dnk|0)j`1N=Dz<<{F{OG+(4B4yE5N|~K zjp7LXHre>=zim4T>d7Jf4n>i5A_pQnk=+V7?8y6)qZ9effI}p;1Z)v_Fv-ED>w~%h z-L&vFlmTVvIWvgozb_@koJ)TNIOnP2`l3~(I5PCeZDrxUw~SFthxVy~Hbfqo9E3KE zc%*jmON|vtjRd1ff2onL=V0z0HyCjsVpwtHF3tI9L;t>jpQdIoO4PRCw>TL6NPWb4 zOd~yjQ9?d_Aljc=eIR6(#3?+yu`t^<2{;)4?PcNyqYgy%XkewluWzLY{Zb68j)dd( zGyZD_qS!pzI>t4UEm~_K)-k_&`_zGJ*OcQp149u6har`pLJ1-BXyQ+y+Jj}9B-{w? zh)|1iRzxUAql%#TG(ex-ZwL}+aym{2YAk%luD32-y7m4`+&wA+Y_P6@A`T-h7t}Nw zm>%V#Zc@=;$ceIEfHsKWlxUr4?7bD(I1-h+g{1G)_wXVbpO;`fSO1~2Pb}&tX zJ1Q@PT~BrRWOqN`$o$4cwhlCMm;6S69!ExMZA-_&73xn5nnA`4;dZ)gyLLCcb10=nIp`bScMYc3ZzE^{J zEKlSFq>Ek%>W=HB6&H**f;2|9pRxXT=%+0P5?-v!{Zi@fGU!Nuc7Sv@Sg0@`u!rsc zh6L>bx}hv$g)>=@?QWh(-ZHfOALG*`1OV)u^=wYFOTPgu z+n{=S-pJCE3x`(SOfb3=%A5&hu7nw5tK3y9omDGcRjVZ%@Us_?QtETP^XLq2U8Yl) z3GvwBQBkpOUA9w~Jz{d{N^hp7ffFoA($eAaaTz0#6LBT?6pixOf?Ke6a@A;>D{c08 z^M^a$+wqTcmYhrZxYb>6cGjCus7|4)RQ2KyGvCX^$m%~n@Vi#`>Tc)iZivfsE3JIg ze&OkJPqPbGxzbh(LfaCjp=7j!ozuY1UdAr7vZXec!G2E>osxfRRxMlC%x-LPZ)|gJ zY@3+16T;b?STU;kBFp3{pEsgQ(HjR<`nqKN#C?^5Jf9 zJL|D!@O*Z`YIgM#?94Woai_-!zOoQ7W+d_p2q~kLW$%>VOOhFKN7nt|Td;Za*#&1{ zW9x;^+=V^?eCI^&o)gfELLmT;I1bJhw!FoazSWbC8`-3no>=)sdY-3n#+c&FzzLnl zSn4*;cN*urj0>*l+>dQ?KDNpA*cQo#HnQcLTrS?vnP@e$J9e?njHd*1G0XVWZ@QU~HBt#4-NP$A$vGoiKToa@9wDChW_AO{7jnXW>CNqIJEz~c0q-E)4ePh@HhANL;R)8kw{lu$v5|RD5h06Y|ogKW;PQY8AY}{KL-GM zsHV6%T=r2}jS7CQ&aBbF&$T5>qt}>ZzmBdcYs!^Wrj@3XMz@3Yl#{r3e*ykd%2 zzd-r>S+$GT&rwcPs^Rv;95q~;SfGKtD_pxlrF7|wo0lkEOVp5;7J|9}*8lakxYDe| z0|Y2i47Jac2u=DW*b^z!BJg8WNQKp9z?6syKP9vY#xjDSQ_9?j(Yyh(fyW~Sk)c9M ze;>hMYt>L;izEWYasu5*yI>mR+aTPZB?eYPH>fq`P z!V4(1vChUEq17oh&$U2|Z;5q5Go@_#rtwA)*$7+fL)FT*ppFkdfUluo{U^iTIRomt7_!Y_hqW6nEN`!Kb+a7WZ1zvHlyC9 zYj~jTco7V_AGt-p0M-zdBO)MNaVU@K(0$a2 z!WD}Z*g~Etu%n1abp%6x{h;#*3`YQllz|Vn^A`#TnZGTJru`5eCWg@@dTekHtqhEV z2nWRX90>i44~Rx3u26_tcrFr^gCK=aQ$i#<&78X#aiOM8XY^rW!3q~YSBUW;ejY7N zoy|f$DHu~gFk9khRsy4=F{uI&1u+LBaso3J`6DLvnE3Dp5kjc(8ZiK?fSgIJ`;QHB zTJ}a#=jB#3%aXJLK$n32f&j!q8TO#ooQPkL!$F)mm58<3dez{pDSno7il!Z;C$KtuJ)y3tf7XU}*4SRH)rne75}HA)FsNCTSDh z=;Dib!;R@2iKlmk%Y!#~s5Y}sF1wbU1?k7~qWVJR$0Zs}XBF4yC_l+jL*CD@2p0f~ zy}!zXH*s7Bisilmxg9RhL&}*Iuu8}DC8S)4#ftC&IVch3|6hC88r;Nng?HB$m{>?6 zma*)$u>yWc2EWqSRj>mNU@$m#04F?(+1N-WZfwa}VW^A4W2Q|>>Hz73kSS?_X;Qmw zg41S-Lo!LGztWj>W`(zvN8pCGevp5ToxW(N({t|Km&6J}o9RqvvZFb>+P(MOyLa#2 zJ@?%2oO6aVaG9r;sPFsPTKIDtaFL1kq}${G86CcFn$;CRp)7_R+Jd=W52L|w-+WIR z@hvcQ@vX^c&nj;+5%JKthla>`e(ULpch3Aq5hRPGMcz_oK|dNFGiY1THbX*GD)#&HM4V;nun9g92Rw&Q&U<+b}E9JTIw15AW^ zd%8mHegZJQ2}pFy)PKQ&d~{Edh~qJ?KS56b|CvF4V=6x9jtufY1_TGm};UA_(#| zZUE-{JSZTv1B^=llc0d&KcTQVFQp2qn@C*Zzu>CqgtwOl4q)M^715(igP4M6Q-Rzz zJX{en(Z!WQ{2M@$R4T!bfoK8Vi6P%qAC`*qd}GKrA>6({kj#f=vh?%e^A~nQK_G*; zT(=MYM%-0ymddw`I<~6rDv7B(nceE~({2^=E&cg)DX&t`{eKbiOsLC2U{;sMH!>!g+=;aJq^6op3YQOaV8WT9}!8$gn3#EzMfNpdwUk@i?8kptXkX zZcD#vR`#tX*=l<{mAaRqamT=tP=di4EFb$V`#~Qzu;A9*`zZo633~4<+7w=G9f)db z?VlGfra{|iYDhgD;Hs3TMcmGTD3G|Bn&B@bZX9e_24M3HPkbN<<(N#iRfPjVq25_3 z4kPMm;0M58vy{~9s`0N((g)0XD!q%ih*6MRLuTsSjl%c9hmhtT{hE5DrwOCd=+5r( zZsR=yf(1Q}FHv8Ec>n{Fo$cT1)2L!Cg4jUc`cx>_89X zyk#r?2Q3tbU4$~uL&Q9k)Dt$nKNIDW*;6okTNWq2El|Q}Dr3L^X?@&qPE!#Pn^eRD ze%kVA&&x7C^*&dU{AQyU&2O~O)anOsWR6)K;XH4&K<#t&%>$g2S8c!ojD;Y8b#sb8 zm!NBLp~LKclD|oLsQUEh5F6Dc21KVe*20LS9fRso|GJ?HC1LB=Qi?xMQWLY5$=7?L zfxkAM>xKOHU$@|wlhn(shq(>)!Zi=FnITpE_ZU)Y7(K)2f_BcI_PH3ooK(vF7Njgk`*MytqFL!{Qf)C)8q7O=cZ3SO#@E@v}$&C1g?@p{NO_P7O~UdkLJZ zOb(o)pdXr`cQY{9+ZE~nY|#F8v9rCSD^QkHv4Bbgs-1wUK)5l0Z)se?)d^ZteQ$W` zxz}Rzn=0?CzPF}|9Qdj#sy$pUw(**^+jeeVQ3Zh$R($l%iy!@DaO%x>CQtkVv>{+Y zJ$3Ho6%{LLCQn?PJU_Go^)$zSNN`++T-U$z(&THugk00lya`#{nCIR~{3+@28$b=E z6@Ox{9Hkl`P1ob5^CSq2!HD@ta55p@g9KIw7!tBUQ4*={U{DQAo*#Ljl(E85ZIs-(bs0hzd7&45At6!e=SeLRmniw-;9=c&Yj3B?zkEE}>97_t#Pox`&Wi z&3zFGmo%LSGnsA!-UD_1DfWipjB=lFx8}f-qAYXHD3D7t@Zdxr^ znTAe>g&#dL_3C;0wh@3KWmZ{10fsa+u=C`^tn?KSdiMb!GPbBQEVPqXP7r&87`%Z_ zeNiVC%Y=Xn``X2QYKm|`+z&W7qKU_^64AwpyP)I5Qqg)jmik1XFHA$$wc+)4BzuvR zU>74Ec#7~S2MKEF#H~m)jebmV&J7dbAOxc83JX8CVl$AK z!=Ri)x47Y0W{()!1CiMSM#Wv2D)>UkjVv@H;1brE=|(-1X2HsGAP@xX-#(!S?6ZaL z_8{!Un-&U^q$|To`in3UA7QR7&N`MphUA(p`}C$!Tk#CHpIw+gkp*BVbQwDAm$O!C zu01hkm{fwkLa*}0QNAw1*NymPevO9PK|qw`T`#*fjO7Cku5_?b=Bv%G0$Rvb9L-vL zIcseK|AVr#e}-cYvc&kfmpjCL%rNWN!`ydUWM@6(D=7JyJzDZeq~wuk$<9d0&e4)x zqs!VPhkLBVD>({b`J&{Db4=}`7WQPzSXPnbT@}e%HRf_(b(cro4@KM$y&sCWcb{z3 zB0rFL5XBAe9hMrlNrl^G=MLbJ>yGlvBmD9~LFVh??m>lowMY*OQp;n~W8IQBD7!*X z#5F!Y%KIX`Pv%#e=Gi8Bx67^_YSQuuUoP`H;LM09Ym>aYWtRYKXi$q0pX^!(8I`)# zMEIJK1v3AjX*o(}1fkJesgCf~!-wC83|rze2-5O8j2Ys6RCaB{^l9YQS4H?$GG8B; zR`PC?U7IMd4V#B_fKHj;5SL1Ap13MxdZW&Yh_hliAD*H1z-hy;9BGkOHc5pK{m$7u z)5qe2`vjuqIkTj!<)axZ-pBxy*=S|M<&1{7jAsuF_6fP`O{Cr+SbW*ZK+GQc9p6DXx1&Y2c=+0>NzNd`lWtE z*(tX5SOX}to?DNltmZc0ZdoH1wg{pWg6}MMG^09_Q9b;mj6MNeG=d3z6CabE?YDjt zf9u+yp%oESCwZ_-so=S)Gd2p^TOjA(9|zTw$~MWlO=J1q!6z=~`>sH+$i;Baf6ip` z@bv=VUg_lwa}U8CQqdmS>A&L420xXK%$fPd%x_cRag3|Ua&oH`^U_E z(1tf=4swh==kzAoR{Zxc4t)dl?xtD*;1vP=m9w!7b|0kFAg#S?OJT}y=B=sR;!cTh z1xQOPfL?Z6;KCjlZnX`DI;vIk62J*avc{`QGB~TK%e8!Pph}=&ZxZ>}Y_@@308Dtn zEa+xmzc{ofZ05N?&$vb6 z05W|keT-**GZjR=&|MnDQlR2;Bq)S>(ukWBYF9w?@I>H1i+zz;9iVJNWeN1|5Qg&% z1&YNoK!y>}`40`<3=97d*JEkI`o2k;SMihmN8kgWYG?O)LHDc(OwJtU7$^6i$~7`H>oR2s3D4z9h>Jk%`PS8ADVs^p{6l5Mhe zI~HbP9JOT8PsEevM=d$t05B zBT2(9K)e>;jYmX?B(Cg_DJ{&J@bPnyNb#ETiW;`Ut)=mV<$y zgD_QBF9*K5C(wfudsxJ;f=Cv{Zr~9rgAD0)#^K(co{+dt#F3f=sEcLM>3Fy-VOmaC z{nOQ}o&K;`j9i`S8RMjeS{;!IiXR^{d8#-}=ui4aS}pCp4~P&|n1LS?Mhp@-#$ee$ zF^jG+wku5f6=nha#+kfv=DrV^mT{(LoLN22RE{&*(@euSQwqm_P3>CDLik>!g#W(* DFKSop literal 0 HcmV?d00001 diff --git a/page_objects/__pycache__/section_mileage_config_page.cpython-312.pyc b/page_objects/__pycache__/section_mileage_config_page.cpython-312.pyc new file mode 100644 index 0000000000000000000000000000000000000000..742ec8612805c5a0309970f5c91f99d8947cf074 GIT binary patch literal 44726 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 In36XC2b}+#mjD0& literal 0 HcmV?d00001 diff --git a/page_objects/__pycache__/upload_config_page.cpython-312.pyc b/page_objects/__pycache__/upload_config_page.cpython-312.pyc new file mode 100644 index 0000000000000000000000000000000000000000..8cc26d12806cf67d8ec05dc4627db4327c21b60c GIT binary patch literal 77778 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 bool: + """ + 向指定TCP端口发送指令 + + 参数: + command: 要发送的指令字符串 + host: 目标主机地址(默认127.0.0.1) + port: 目标端口(默认8888) + encoding: 字符串编码格式(默认utf-8) + + 返回: + 发送成功返回True,失败返回False + """ + # 创建TCP socket并自动关闭(with语句确保资源释放) + with socket.socket(socket.AF_INET, socket.SOCK_STREAM) as sock: + try: + # 连接服务器(超时时间5秒,避免无限阻塞) + sock.settimeout(5.0) + sock.connect((host, port)) + + # 发送指令(转换为字节流) + sock.sendall(command.encode(encoding)) + print(f"指令 '{command}' 发送成功") + return True + + except ConnectionRefusedError: + print(f"连接失败:{host}:{port} 未监听或不可达") + except socket.timeout: + print(f"连接超时:超过5秒未连接到 {host}:{port}") + except UnicodeEncodeError: + print(f"编码失败:指令包含{encoding}无法编码的字符") + except Exception as e: + print(f"发送失败:{str(e)}") + + return False + + +# 使用示例 +if __name__ == "__main__": + # 发送StartConnect指令 + send_tcp_command("StartConnect") + + # 也可以发送其他指令,例如: + # send_tcp_command("StopConnect") \ No newline at end of file diff --git a/page_objects/download_tabbar_page.py b/page_objects/download_tabbar_page.py new file mode 100644 index 0000000..e753889 --- /dev/null +++ b/page_objects/download_tabbar_page.py @@ -0,0 +1,319 @@ +# 更新基站页面操作 +# page_objects/download_tabbar_page.py +from appium.webdriver.common.appiumby import AppiumBy +from selenium.webdriver.support.ui import WebDriverWait +from selenium.webdriver.support import expected_conditions as EC +from selenium.common.exceptions import TimeoutException, NoSuchElementException, StaleElementReferenceException +import logging +import time +from datetime import datetime + +import globals.ids as ids # 导入元素ID +import globals.global_variable as global_variable # 导入全局变量 +from globals.driver_utils import check_session_valid, reconnect_driver + +class DownloadTabbarPage: + def __init__(self, driver, wait, device_id): + self.driver = driver + self.wait = wait + self.device_id = device_id + self.logger = logging.getLogger(__name__) + # 添加默认的目标日期值 + self.target_year = 2022 + self.target_month = 9 + self.target_day = 22 + + def is_download_tabbar_visible(self): + """检查下载标签栏是否可见""" + try: + return self.driver.find_element(AppiumBy.ID, ids.DOWNLOAD_TABBAR_ID).is_displayed() + except NoSuchElementException: + self.logger.warning("下载标签栏元素未找到") + return False + except Exception as e: + self.logger.error(f"检查下载标签栏可见性时发生意外错误: {str(e)}") + return False + + def click_download_tabbar(self): + """点击下载标签栏""" + try: + download_tab = self.wait.until( + EC.element_to_be_clickable((AppiumBy.ID, ids.DOWNLOAD_TABBAR_ID)) + ) + download_tab.click() + self.logger.info("已点击下载标签栏") + + # 使用显式等待替代固定等待 + self.wait.until( + lambda driver: self.is_download_tabbar_visible() + ) + return True + except TimeoutException: + self.logger.error("等待下载标签栏可点击超时") + return False + except Exception as e: + self.logger.error(f"点击下载标签栏时出错: {str(e)}") + return False + + def update_work_base(self): + """更新工作基点""" + try: + # 点击更新工作基点 + update_work_base = self.wait.until( + EC.element_to_be_clickable((AppiumBy.ID, ids.UPDATE_WORK_BASE)) + ) + update_work_base.click() + self.logger.info("已点击更新工作基点") + + # 等待更新完成 - 可以添加更具体的等待条件 + # 例如等待某个进度条消失或成功提示出现 + time.sleep(2) # 暂时保留,但建议替换为显式等待 + return True + except TimeoutException: + self.logger.error("等待更新工作基点按钮可点击超时") + return False + except Exception as e: + self.logger.error(f"更新工作基点时出错: {str(e)}") + return False + + def _get_current_date(self): + """获取当前开始日期控件的日期值,支持多种格式解析""" + try: + date_element = self.wait.until( + EC.visibility_of_element_located((AppiumBy.ID, ids.DATE_START)) + ) + date_text = date_element.text.strip() + self.logger.info(f"获取到当前开始日期: {date_text}") + + # 尝试多种日期格式解析 + date_formats = [ + "%Y-%m-%d", # 匹配 '2025-08-12' 格式 + "%Y年%m月%d日", # 匹配 '2025年08月12日' 格式 + "%Y/%m/%d" # 可选:添加其他可能的格式 + ] + + for fmt in date_formats: + try: + return datetime.strptime(date_text, fmt) + except ValueError: + continue # 尝试下一种格式 + + # 如果所有格式都匹配失败 + self.logger.error(f"日期格式解析错误: 无法识别的格式,日期文本: {date_text}") + return None + + except TimeoutException: + self.logger.error("获取当前日期超时") + return None + except Exception as e: + self.logger.error(f"获取当前日期失败: {str(e)}") + return None + + def update_level_line(self): + """更新水准线路,修改为设置2022年9月22日""" + try: + # 点击更新水准线路 + update_level_line = self.wait.until( + EC.element_to_be_clickable((AppiumBy.ID, ids.UPDATE_LEVEL_LINE)) + ) + update_level_line.click() + self.logger.info("已点击更新水准线路") + + # 获取原始开始日期 + original_date = self._get_current_date() + if not original_date: + self.logger.error("无法获取原始开始日期,更新水准线路失败") + return False + + # 点击开始日期 + date_start = self.wait.until( + EC.element_to_be_clickable((AppiumBy.ID, ids.DATE_START)) + ) + date_start.click() + self.logger.info("已点击开始日期控件") + + # 处理时间选择器,设置为2022年9月22日 + if not self.handle_time_selector(2022, 9, 22, original_date): + self.logger.error("处理时间选择失败") + return False + + return True + except TimeoutException: + self.logger.error("等待更新水准线路按钮可点击超时") + return False + except Exception as e: + self.logger.error(f"更新水准线路时出错: {str(e)}") + return False + + def _scroll_to_value(self, picker_id, target_value, original_value, max_attempts=20): + """滚动选择器到目标值,基于原始值计算滚动次数""" + try: + # 计算需要滚动的次数(绝对值) + scroll_count = abs(int(target_value) - int(original_value)) + self.logger.info(f"需要滚动{scroll_count}次将{picker_id}从{original_value}调整到{target_value}") + + # 确定滚动方向 + direction = "down" if int(target_value) > int(original_value) else "up" + + # 获取选择器元素 + picker = self.wait.until( + EC.visibility_of_element_located((AppiumBy.ID, picker_id)) + ) + + # 计算滚动坐标 + x = picker.location['x'] + picker.size['width'] // 2 + self.logger.info(f"水平位置x为{x}") + y_center = picker.location['y'] + picker.size['height'] // 2 + self.logger.info(f"垂直位置中点y_center为{y_center}") + # start_y = y_center if direction == "down" else picker.location['y'] + # end_y = picker.location['y'] if direction == "down" else y_center + # 关键修改:计算选择器高度的五分之一(滑动距离) + height_fifth = picker.size['height'] // 5 # 1/5高度 + + # 根据方向计算起点和终点,确保滑动距离为 height_fifth + if direction == "down": + # 向下滚动:从中心点向上滑动1/5高度 + start_y = y_center + end_y = y_center - height_fifth # 终点 = 中心点 - 1/5高度 + self.logger.info(f"down垂直开始位置start_y为{y_center},垂直结束位置end_y为{end_y}") + + else: + # 向上滚动:从中心点向下滑动1/5高度 + start_y = y_center + end_y = y_center + height_fifth # 终点 = 中心点 + 1/5高度 + self.logger.info(f"up垂直开始位置start_y为{y_center},垂直结束位置end_y为{end_y}") + # 执行滚动操作 + for _ in range(scroll_count): + self.driver.swipe(x, start_y, x, end_y, 500) + time.sleep(0.5) # 等待滚动稳定 + return True # 循环scroll_count次后直接返回 + # # 验证当前值 + # current_value = picker.text + # if current_value == str(target_value): + # self.logger.info(f"{picker_id}已达到目标值: {target_value}") + # return True + + # 最终验证 + # final_value = picker.text + # if final_value == str(target_value): + # self.logger.info(f"{picker_id}已达到目标值: {target_value}") + # return True + # else: + # self.logger.error(f"{picker_id}滚动{scroll_count}次后未达到目标值,当前值: {final_value}") + # return False + + except StaleElementReferenceException: + self.logger.warning("元素状态已过期,重新获取") + return False + except Exception as e: + self.logger.error(f"滚动选择器出错: {str(e)}") + return False + + def handle_time_selector(self, target_year, target_month, target_day, original_date=None): + """处理时间选择器,选择起始时间并确认""" + self.logger.info(f"传入handle_time_selector的初始日期: {original_date}") + try: + # 等待时间选择器出现 + self.wait.until( + EC.visibility_of_element_located((AppiumBy.ID, ids.ALERT_DIALOG)) + ) + self.logger.info("时间选择对话框已出现") + + # 如果没有提供原始日期,使用当前日期控件的值 + if not original_date: + original_date = self._get_current_date() + if not original_date: + self.logger.error("无法获取原始日期,处理时间选择失败") + return False + + # 滚动选择年份 + if not self._scroll_to_value(ids.SCRCOLL_YEAR, target_year, original_date.year): + self.logger.error("滚动选择年份失败") + return False + + # 滚动选择月份 + if not self._scroll_to_value(ids.SCRCOLL_MONTH, target_month, original_date.month): + self.logger.error("滚动选择月份失败") + return False + + # 滚动选择日期 + if not self._scroll_to_value(ids.SCRCOLL_DAY, target_day, original_date.day): + self.logger.error("滚动选择日期失败") + return False + + # 点击确认按钮 + confirm_btn = self.wait.until( + EC.element_to_be_clickable((AppiumBy.ID, ids.SCRCOLL_CONFIRM)) # 日期选择器确认按钮 + ) + confirm_btn.click() + self.logger.info("已确认时间选择") + + # 点击对话框确认按钮(UPDATE_LEVEL_LINE_CONFIRM) + confirm_btn = self.wait.until( + EC.element_to_be_clickable((AppiumBy.ID, ids.UPDATE_LEVEL_LINE_CONFIRM)) # 选择日期对话框确认按钮 + ) + confirm_btn.click() + self.logger.info("已点击对话框确认按钮commit") + + # 新增:等待加载对话框出现 + custom_dialog = self.wait.until( + EC.visibility_of_element_located((AppiumBy.ID, ids.LOADING_DIALOG)) + ) + self.logger.info("检测到loading对话框出现") + + if not check_session_valid(self.driver, self.device_id): + self.logger.warning(f"设备 {self.device_id} 会话无效,尝试重新连接驱动...") + if not reconnect_driver(self.device_id, self.driver): + self.logger.error(f"设备 {self.device_id} 驱动重连失败") + + # 新增:等待加载对话框消失(表示更新完成) + WebDriverWait(self.driver, 300).until( + EC.invisibility_of_element_located((AppiumBy.ID, ids.LOADING_DIALOG)) + ) + self.logger.info("loading对话框已消失,更新完成") + + '''点击commit确认按钮后,loading弹窗会出现,等待其加载完成后关闭 + 检测导航栏中的测量tabbar是否出现来确定是否返回True + ''' + # measure_tabbar_btn = self.wait.until( + # EC.visibility_of_element_located((AppiumBy.ID, ids.MEASURE_TABBAR_ID)) + # ) + # self.logger.info("检测测量tabbar按钮出现") + + return True + + except TimeoutException as e: + # 明确超时发生在哪个环节 + self.logger.error("处理时间选择时超时:可能是等待对话框出现、日期选择器元素或确认按钮超时", exc_info=True) + return False + except Exception as e: + # 细分不同环节的异常 + self.logger.error(f"处理时间选择对话框时出错:可能是日期滚动、选择器确认或对话框确认环节失败 - {str(e)}", exc_info=True) + return False + + def download_tabbar_page_manager(self): + """执行基础更新操作""" + try: + # 执行基础更新流程 + self.logger.info(f"设备 {global_variable.GLOBAL_DEVICE_ID} 开始执行更新流程") + + # 点击下载标签栏 + if not self.click_download_tabbar(): + self.logger.error(f"设备 {global_variable.GLOBAL_DEVICE_ID} 点击下载标签栏失败") + return False + + # 更新工作基点 + if not self.update_work_base(): + self.logger.error(f"设备 {global_variable.GLOBAL_DEVICE_ID} 更新工作基点失败") + return False + + # 更新水准线路 + if not self.update_level_line(): + self.logger.error(f"设备 {global_variable.GLOBAL_DEVICE_ID} 更新水准线路失败") + return False + + self.logger.info(f"设备 {global_variable.GLOBAL_DEVICE_ID} 更新操作执行成功") + return True + except Exception as e: + self.logger.error(f"设备 {global_variable.GLOBAL_DEVICE_ID} 执行更新操作时出错: {str(e)}") + return False \ No newline at end of file diff --git a/page_objects/login_page.py b/page_objects/login_page.py new file mode 100644 index 0000000..f02b3a2 --- /dev/null +++ b/page_objects/login_page.py @@ -0,0 +1,78 @@ + # 登录页面操作 +# page_objects/login_page.py +from appium.webdriver.common.appiumby import AppiumBy +from selenium.webdriver.support.ui import WebDriverWait +from selenium.webdriver.support import expected_conditions as EC +from selenium.common.exceptions import TimeoutException, NoSuchElementException +import logging +import time + +import globals.ids as ids +import globals.global_variable as global_variable # 导入全局变量模块 + +class LoginPage: + def __init__(self, driver, wait): + self.driver = driver + self.wait = wait + self.logger = logging.getLogger(__name__) + + def is_login_page(self): + """检查当前是否为登录页面""" + try: + return self.driver.find_element(AppiumBy.ID, ids.LOGIN_BTN).is_displayed() + except NoSuchElementException: + return False + + def login(self): + """执行登录操作""" + try: + self.logger.info("正在执行登录操作...") + + # 获取文本框中已有的用户名 + username_field = self.wait.until( + EC.element_to_be_clickable((AppiumBy.ID, ids.LOGIN_USERNAME)) + ) + + # 读取文本框内已有的用户名(.text属性获取元素显示的文本内容) + existing_username = username_field.text + # 3. 将获取到的用户名写入全局变量中 + global_variable.GLOBAL_USERNAME = existing_username # 关键:给全局变量赋值 + + # 日志记录获取到的已有用户名(若为空,也需明确记录,避免后续误解) + if existing_username.strip(): # 去除空格后判断是否有有效内容 + self.logger.info(f"已获取文本框中的已有用户名: {existing_username}") + else: + self.logger.info("文本框中未检测到已有用户名(内容为空)") + + # 点击登录按钮 + login_btn = self.wait.until( + EC.element_to_be_clickable((AppiumBy.ID, ids.LOGIN_BTN)) + ) + login_btn.click() + self.logger.info("已点击登录按钮") + + # 等待登录完成 + time.sleep(3) + + # 检查是否登录成功 + if self.is_login_successful(): + self.logger.info("登录成功") + return True + else: + self.logger.warning("登录后未检测到主页面元素") + return False + + except Exception as e: + self.logger.error(f"登录过程中出错: {str(e)}") + return False + + def is_login_successful(self): + """检查登录是否成功""" + try: + # 等待主页面元素出现 + self.wait.until( + EC.presence_of_element_located((AppiumBy.ID, ids.DOWNLOAD_TABBAR_ID)) + ) + return True + except TimeoutException: + return False \ No newline at end of file diff --git a/page_objects/more_download_page.py b/page_objects/more_download_page.py new file mode 100644 index 0000000..cb4c08a --- /dev/null +++ b/page_objects/more_download_page.py @@ -0,0 +1,506 @@ +# test_more_download_page.py +from appium.webdriver.common.appiumby import AppiumBy +from selenium.webdriver.support.ui import WebDriverWait +from selenium.webdriver.support import expected_conditions as EC +from selenium.common.exceptions import TimeoutException, NoSuchElementException +import logging +import time +from globals.driver_utils import launch_app_manually +from page_objects.login_page import LoginPage + +class MoreDownloadPage: + def __init__(self, driver, wait,device_id): + self.driver = driver + self.wait = wait + self.device_id = device_id + self.logger = logging.getLogger(__name__) + + def is_on_more_download_page(self): + """通过下载历史数据按钮来判断是否在更多下载页面""" + try: + # 使用下载历史数据按钮的resource-id来检查 + download_history_locator = (AppiumBy.ID, "com.bjjw.cjgc:id/download_history") + self.wait.until(EC.presence_of_element_located(download_history_locator)) + self.logger.info("已确认在更多下载页面") + return True + except TimeoutException: + self.logger.warning("未找到下载历史数据按钮,不在更多下载页面") + return False + except Exception as e: + self.logger.error(f"检查更多下载页面时发生意外错误: {str(e)}") + return False + + def click_download_button(self): + """点击下载按钮""" + try: + # 点击下载历史数据按钮 + download_button = self.wait.until( + EC.element_to_be_clickable((AppiumBy.ID, "com.bjjw.cjgc:id/download_history")) + ) + download_button.click() + self.logger.info("已点击下载历史数据按钮") + + # 等待下载操作开始 + # time.sleep(3) + + return True + + except TimeoutException: + self.logger.error("等待下载按钮可点击超时") + return False + except Exception as e: + self.logger.error(f"点击下载按钮时出错: {str(e)}") + return False + + def click_download_original_data(self): + """点击下载原始数据按钮并处理日期选择""" + try: + # 点击下载原始数据按钮 + download_original_btn = self.wait.until( + EC.element_to_be_clickable((AppiumBy.ID, "com.bjjw.cjgc:id/download_org")) + ) + download_original_btn.click() + self.logger.info("已点击下载原始数据按钮") + + # 等待日期选择弹窗出现 + # time.sleep(2) + + # 点击选择开始日期 + start_date_btn = self.wait.until( + EC.element_to_be_clickable((AppiumBy.ID, "com.bjjw.cjgc:id/date")) + ) + start_date_btn.click() + self.logger.info("已点击选择开始日期") + + # 等待日期选择器出现 + # time.sleep(2) + + # 滑动年份选择器 - 向上滑动1/5的距离 + if not self._swipe_year_wheel(): + self.logger.error("滑动年份选择器失败") + return False + + # 点击日期选择器的确定按钮 + confirm_btn = self.wait.until( + EC.element_to_be_clickable((AppiumBy.ID, "com.bjjw.cjgc:id/okBtn")) + ) + confirm_btn.click() + self.logger.info("已确认日期选择") + + # 等待日期选择器关闭 + # time.sleep(2) + + # 假设弹窗有确定按钮,点击它开始下载 + try: + # 尝试查找并点击下载弹窗的确定按钮 + download_confirm_btn = WebDriverWait(self.driver, 5).until( + EC.element_to_be_clickable((AppiumBy.XPATH, "//android.widget.Button[contains(@text, '确定') or contains(@text, '下载')]")) + ) + download_confirm_btn.click() + self.logger.info("已点击下载确认按钮") + except TimeoutException: + self.logger.warning("未找到下载确认按钮,可能不需要确认") + + # 等待下载开始 + # time.sleep(3) + + return True + + except TimeoutException: + self.logger.error("等待下载原始数据按钮可点击超时") + return False + except Exception as e: + self.logger.error(f"点击下载原始数据时出错: {str(e)}") + return False + + def click_download_result_data(self): + """点击下载成果数据按钮并处理日期选择""" + try: + # 点击下载成果数据按钮 + download_result_btn = self.wait.until( + EC.element_to_be_clickable((AppiumBy.ID, "com.bjjw.cjgc:id/download_result")) + ) + download_result_btn.click() + self.logger.info("已点击下载成果数据按钮") + + # 等待日期选择弹窗出现 + # time.sleep(2) + + # 点击选择开始日期 + start_date_btn = self.wait.until( + EC.element_to_be_clickable((AppiumBy.ID, "com.bjjw.cjgc:id/date")) + ) + start_date_btn.click() + self.logger.info("已点击选择开始日期") + + # 等待日期选择器出现 + # time.sleep(2) + + # 滑动年份选择器 - 向上滑动1/5的距离 + if not self._swipe_year_wheel(): + self.logger.error("滑动年份选择器失败") + return False + + # 点击日期选择器的确定按钮 + confirm_btn = self.wait.until( + EC.element_to_be_clickable((AppiumBy.ID, "com.bjjw.cjgc:id/okBtn")) + ) + confirm_btn.click() + self.logger.info("已确认日期选择") + + # 等待日期选择器关闭 + # time.sleep(2) + + # 假设弹窗有确定按钮,点击它开始下载 + try: + # 尝试查找并点击下载弹窗的确定按钮 + download_confirm_btn = WebDriverWait(self.driver, 5).until( + EC.element_to_be_clickable((AppiumBy.XPATH, "//android.widget.Button[contains(@text, '确定') or contains(@text, '下载')]")) + ) + download_confirm_btn.click() + self.logger.info("已点击下载确认按钮") + except TimeoutException: + self.logger.warning("未找到下载确认按钮,可能不需要确认") + + # 等待下载开始 + # time.sleep(3) + + return True + + except TimeoutException: + self.logger.error("等待下载成果数据按钮可点击超时") + return False + except Exception as e: + self.logger.error(f"点击下载成果数据时出错: {str(e)}") + return False + + def _swipe_year_wheel(self): + """滑动年份选择器的滚轮""" + try: + # 获取年份选择器滚轮元素 + year_wheel = self.driver.find_element(AppiumBy.ID, "com.bjjw.cjgc:id/wheelView2") + + # 获取滚轮的位置和尺寸 + location = year_wheel.location + size = year_wheel.size + + # 计算滚轮中心点坐标 + center_x = location['x'] + size['width'] // 2 + center_y = location['y'] + size['height'] // 2 + + # 计算滑动距离 - 滚轮高度的1/5 + swipe_distance = size['height'] // 5 + + for i in range(3): + # 执行滑动操作 - 从中心向上滑动1/5高度 + self.driver.swipe(center_x, center_y - swipe_distance, center_x, center_y, 500) + + self.logger.info(f"已{i}次滑动月份选择器") + return True + + except Exception as e: + self.logger.error(f"滑动年份选择器时出错: {str(e)}") + return False + + # def wait_for_loading_dialog(self, timeout=900, download_type="unknown"): + # """ + # 检查特定结构的加载弹窗的出现和消失 + + # 参数: + # timeout: 最大等待时间,默认10分钟(600秒) + + # 返回: + # bool: 如果加载弹窗出现并消失返回True,否则返回False + # """ + # try: + # self.logger.info(f"开始检查{download_type}加载弹窗...") + + # # 首先检查加载弹窗是否出现 + # start_time = time.time() + # loading_appeared = False + + # # 等待加载弹窗出现(最多等待30秒) + # while time.time() - start_time < 30: + # try: + # # 根据提供的结构查找加载弹窗 + # # 查找包含ProgressBar和"loading..."文本的弹窗 + # loading_indicators = [ + # (AppiumBy.XPATH, "//android.widget.FrameLayout[@resource-id='android:id/content']/android.widget.LinearLayout[@resource-id='android:id/parentPanel']//android.widget.ProgressBar"), + # (AppiumBy.XPATH, "//android.widget.TextView[@resource-id='android:id/message' and @text='loading...']"), + # (AppiumBy.XPATH, "//android.widget.FrameLayout[@resource-id='android:id/content']//android.widget.ProgressBar"), + # (AppiumBy.XPATH, "//*[contains(@text, 'loading...')]") + # ] + + # for by, value in loading_indicators: + # try: + # element = self.driver.find_element(by, value) + # if element.is_displayed(): + # loading_appeared = True + # self.logger.info("数据下载已开始") + # self.logger.info("检测到加载弹窗出现") + # break + # except: + # continue + + # if loading_appeared: + # break + + # except Exception as e: + # pass + + # time.sleep(1) + + # # 如果加载弹窗没有出现,直接返回True + # if not loading_appeared: + # self.logger.info("未检测到加载弹窗,继续执行") + # return True + + # # 等待加载弹窗消失 + # self.logger.info("等待加载弹窗消失...") + # disappearance_start_time = time.time() + + # while time.time() - disappearance_start_time < timeout: + # try: + # # 检查加载弹窗是否还存在 + # loading_still_exists = False + + # for by, value in loading_indicators: + # try: + # element = self.driver.find_element(by, value) + # if element.is_displayed(): + # loading_still_exists = True + # break + # except: + # continue + + # if not loading_still_exists: + # self.logger.info("加载弹窗已消失") + # return True + + # # 每1分钟记录一次状态 + # if int(time.time() - disappearance_start_time) % 60 == 0: + # elapsed_time = int(time.time() - disappearance_start_time) + # self.logger.info(f"加载弹窗仍在显示,已等待{elapsed_time//60}分钟") + + # except Exception as e: + # # 如果出现异常,可能弹窗已经消失 + # self.logger.info("加载弹窗可能已消失") + # return True + + # time.sleep(1) + + # # 如果超时,记录错误并返回False + # self.logger.error(f"加载弹窗在{timeout}秒后仍未消失") + # # return False + # # 检查是否有loading加载窗口 + # try: + # self.logger.info(f"检查{download_type}下载是否有loading加载窗口...") + # loading_indicators = [ + # (AppiumBy.XPATH, "//android.widget.FrameLayout[@resource-id='android:id/content']/android.widget.LinearLayout[@resource-id='android:id/parentPanel']//android.widget.ProgressBar"), + # (AppiumBy.XPATH, "//android.widget.TextView[@resource-id='android:id/message' and @text='loading...']"), + # (AppiumBy.XPATH, "//android.widget.FrameLayout[@resource-id='android:id/content']//android.widget.ProgressBar"), + # (AppiumBy.XPATH, "//*[contains(@text, 'loading...')]") + # ] + + # loading_exists = False + # for by, value in loading_indicators: + # try: + # element = self.driver.find_element(by, value) + # if element.is_displayed(): + # loading_exists = True + # break + # except: + # continue + + # if loading_exists: + # self.logger.info(f"检测到{download_type}下载的loading加载窗口,执行重新打开应用操作") + + + # # 手动启动应用 + # launch_app_manually(self.driver, self.device_id, "com.bjjw.cjgc", ".activity.LoginActivity") + # self.logger.info("已重新启动沉降观测应用") + + # # 点击登录 + # login_page = LoginPage(self.driver, self.wait) + # if login_page.is_login_page(): + # if login_page.login(): + # self.logger.info("登录成功") + # else: + # self.logger.error("登录失败") + # return False + # else: + # self.logger.info("应用已登录,无需重复登录") + + # self.logger.info(f"{download_type}下载超时处理完成,应用已重启") + # # 点击img_5_layout(更多下载按钮) + # more_download_btn = self.wait.until( + # EC.element_to_be_clickable((AppiumBy.ID, "com.bjjw.cjgc:id/img_5_layout")) + # ) + # more_download_btn.click() + # self.logger.info("已点击更多下载按钮") + # self.more_download_page_manager_2(download_type) + # # 等待页面加载 + # time.sleep(1) + # else: + # self.logger.info(f"未检测到{download_type}下载的loading加载窗口") + # except Exception as e: + # self.logger.error(f"检查{download_type}下载的loading加载窗口时出错: {str(e)}") + # # 出错时继续执行,不影响主流程 + + # return False + + # except Exception as e: + # self.logger.error(f"检查加载弹窗时出错: {str(e)}") + # return False + + def _is_loading_present(self): + """私有辅助方法:检测当前页面是否存在加载弹窗""" + loading_indicators = [ + (AppiumBy.XPATH, "//android.widget.FrameLayout[@resource-id='android:id/content']//android.widget.ProgressBar"), + (AppiumBy.XPATH, "//android.widget.TextView[@resource-id='android:id/message' and @text='loading...']"), + (AppiumBy.XPATH, "//*[contains(@text, 'loading...')]") + ] + for by, value in loading_indicators: + try: + element = self.driver.find_element(by, value) + if element.is_displayed(): + return True + except: + continue + return False + + def wait_for_loading_dialog(self, timeout=900, download_type="unknown", retry_count=0): + """ + 检查加载弹窗的出现和消失,支持最多1次重试(总共执行2次) + """ + try: + self.logger.info(f"开始检查 {download_type} 加载弹窗 (尝试次数: {retry_count + 1})...") + + # 1. 等待加载弹窗出现(最多30秒) + start_time = time.time() + loading_appeared = False + while time.time() - start_time < 30: + if self._is_loading_present(): + loading_appeared = True + self.logger.info(f"检测到 {download_type} 加载弹窗出现") + break + time.sleep(1) + + if not loading_appeared: + self.logger.info(f"未检测到 {download_type} 加载弹窗,视为直接通过") + return True + + # 2. 等待加载弹窗消失 + disappearance_start_time = time.time() + while time.time() - disappearance_start_time < timeout: + if not self._is_loading_present(): + self.logger.info(f"{download_type} 加载弹窗已消失") + return True + + # 每60秒打印一次日志 + if int(time.time() - disappearance_start_time) % 60 == 0: + self.logger.info(f"等待中...已耗时 {int(time.time() - disappearance_start_time)//60} 分钟") + time.sleep(2) + + # 3. 超时处理逻辑 + self.logger.error(f"{download_type} 加载弹窗在 {timeout} 秒后仍未消失") + + # 检查是否还可以重试(retry_count=0 时执行重试,即第二次执行) + if retry_count < 1: + self.logger.warning(f"检测到超时,准备进行第 {retry_count + 2} 次尝试(重启应用)...") + + # 执行重启逻辑 + from globals.driver_utils import launch_app_manually + from page_objects.login_page import LoginPage + + launch_app_manually(self.driver, self.device_id, "com.bjjw.cjgc", ".activity.LoginActivity") + login_page = LoginPage(self.driver, self.wait) + if login_page.is_login_page(): + login_page.login() + + # 重新导航到下载页面 + more_download_btn = self.wait.until( + EC.element_to_be_clickable((AppiumBy.ID, "com.bjjw.cjgc:id/img_5_layout")) + ) + more_download_btn.click() + + # 递归调用:增加 retry_count + return self.more_download_page_manager_2(download_type, retry_count = retry_count + 1) + else: + self.logger.error(f"{download_type} 已达到最大重试次数,操作失败") + return False + + except Exception as e: + self.logger.error(f"检查加载弹窗时发生异常: {str(e)}") + return False + + def more_download_page_manager_2(self, download_type, retry_count=0): + """ + 修改后的 manager_2,透传 retry_count 参数 + """ + try: + if not self.is_on_more_download_page(): + return False + + self.click_download_button() + + if download_type in ["原始数据", "历史数据"]: + self.click_download_original_data() + return self.wait_for_loading_dialog(download_type="原始数据", retry_count=retry_count) + + elif download_type == "成果数据": + self.click_download_result_data() + return self.wait_for_loading_dialog(download_type="成果数据", retry_count=retry_count) + + return False + except Exception as e: + self.logger.error(f"manager_2 执行出错: {str(e)}") + return False + + def more_download_page_manager(self): + """执行更多下载页面管理操作""" + try: + self.logger.info("开始执行更多下载页面操作") + + # 检查是否在更多下载页面 + if not self.is_on_more_download_page(): + self.logger.error("不在更多下载页面") + return False + + # 点击下载历史数据按钮 + if not self.click_download_button(): + self.logger.error("点击下载历史数据按钮失败") + return False + + # 等待下载历史数据页面加载完成 + # time.sleep(3) + + # 点击下载原始数据按钮 + if not self.click_download_original_data(): + self.logger.error("点击下载原始数据按钮失败") + return False + + # 等待下载操作完成 + time.sleep(1) + + # 使用wait_for_loading_dialog函数等待下载过程中的加载弹窗消失 + if not self.wait_for_loading_dialog(download_type="原始数据"): + self.logger.warning("下载过程中的加载弹窗未在预期时间内消失,但操作已完成") + + # 等待一段时间,确保原始数据下载完成 + time.sleep(1) + + # 点击下载成果数据按钮 + if not self.click_download_result_data(): + self.logger.error("点击下载成果数据按钮失败") + return False + + # 使用wait_for_loading_dialog函数等待下载过程中的加载弹窗消失 + if not self.wait_for_loading_dialog(download_type="成果数据"): + self.logger.warning("成果数据下载过程中的加载弹窗未在预期时间内消失,但操作已完成") + + self.logger.info("更多下载页面操作执行完成") + return True + except Exception as e: + self.logger.error(f"执行更多下载页面操作时出错: {str(e)}") + return False \ No newline at end of file diff --git a/page_objects/screenshot_page.py b/page_objects/screenshot_page.py new file mode 100644 index 0000000..15e3a83 --- /dev/null +++ b/page_objects/screenshot_page.py @@ -0,0 +1,1471 @@ +# screenshot_page.py +import subprocess +import logging +import time +import re +import os +from datetime import datetime +from appium.webdriver.common.appiumby import AppiumBy +from selenium.common.exceptions import NoSuchElementException, TimeoutException +from selenium.webdriver.support.ui import WebDriverWait +from selenium.webdriver.support import expected_conditions as EC +import globals.global_variable as global_variable # 导入全局变量模块 +import globals.ids as ids +# 导入全局驱动工具函数 +from globals.driver_utils import check_session_valid, reconnect_driver +import globals.driver_utils as driver_utils +import globals.global_variable as global_variable # 导入全局变量模块 + +class ScreenshotPage: + def __init__(self, driver, wait, device_id=None): + self.driver = driver + self.wait = wait + self.device_id = device_id + self.logger = logging.getLogger(__name__) + self.all_items = set() + + def scroll_list(self, direction="down"): + """滑动列表以加载更多项目 + + Args: + direction: 滑动方向,"down"表示向下滑动,"up"表示向上滑动 + + Returns: + bool: 滑动是否成功执行,对于向上滑动,如果滑动到顶则返回False + """ + try: + # 获取列表容器 + list_container = self.driver.find_element(AppiumBy.ID, ids.MEASURE_LIST_ID) + + # 计算滑动坐标 + start_x = list_container.location['x'] + list_container.size['width'] // 2 + + if direction == "down": + # 向下滑动 + start_y = list_container.location['y'] + list_container.size['height'] * 0.95 + end_y = list_container.location['y'] + list_container.size['height'] * 0.05 + self.logger.info("向下滑动列表") + else: + # 向上滑动 + # 记录滑动前的项目,用于判断是否滑动到顶 + before_scroll_items = self.get_current_items() + start_y = list_container.location['y'] + list_container.size['height'] * 0.05 + end_y = list_container.location['y'] + list_container.size['height'] * 0.95 + self.logger.info("向上滑动列表") + + # 执行滑动 + self.driver.swipe(start_x, start_y, start_x, end_y, 1000) + + + # 向上滑动时,检查是否滑动到顶 + if direction == "up": + after_scroll_items = self.get_current_items() + # 如果滑动后的项目与滑动前的项目相同,说明已经滑动到顶 + if after_scroll_items == before_scroll_items: + self.logger.info("已滑动到列表顶部,列表内容不变") + return False + + return True + except Exception as e: + self.logger.error(f"滑动列表失败: {str(e)}") + return False + + + def get_current_items(self): + """获取当前页面中的所有项目文本""" + try: + items = self.driver.find_elements(AppiumBy.ID, ids.MEASURE_LISTVIEW_ID) + item_texts = [] + + for item in items: + try: + title_element = item.find_element(AppiumBy.ID, ids.MEASURE_NAME_TEXT_ID) + if title_element and title_element.text: + item_texts.append(title_element.text) + except NoSuchElementException: + continue + + return item_texts + except Exception as e: + self.logger.error(f"获取当前项目失败: {str(e)}") + return [] + + def click_item_by_text(self, text): + """点击指定文本的项目""" + try: + # 查找包含指定文本的项目 + items = self.driver.find_elements(AppiumBy.ID, ids.MEASURE_LISTVIEW_ID) + + for item in items: + try: + title_element = item.find_element(AppiumBy.ID, ids.MEASURE_NAME_TEXT_ID) + if title_element and title_element.text == text: + title_element.click() + self.logger.info(f"已点击项目: {text}") + return True + except NoSuchElementException: + continue + + self.logger.warning(f"未找到可点击的项目: {text}") + return False + except Exception as e: + self.logger.error(f"点击项目失败: {str(e)}") + return False + + + + def find_keyword(self, fixed_filename): + """查找指定关键词并点击,支持向下和向上滑动查找""" + try: + if not check_session_valid(self.driver, self.device_id): + self.logger.warning(f"设备 {self.device_id} 会话无效,尝试重新连接驱动...") + if not reconnect_driver(self.device_id, self.driver): + self.logger.error(f"设备 {self.device_id} 驱动重连失败") + + # 等待线路列表容器出现 + self.wait.until( + EC.presence_of_element_located((AppiumBy.ID, ids.MEASURE_LIST_ID)) + ) + self.logger.info("线路列表容器已找到") + + max_scroll_attempts = 100 # 最大滚动尝试次数 + scroll_count = 0 + previous_items = set() # 记录前一次获取的项目集合,用于检测是否到达边界 + + # 首先尝试向下滑动查找 + while scroll_count < max_scroll_attempts: + # 获取当前页面中的所有项目 + current_items = self.get_current_items() + # self.logger.info(f"当前页面找到 {len(current_items)} 个项目: {current_items}") + + # 检查目标文件是否在当前页面中 + if fixed_filename in current_items: + self.logger.info(f"找到目标文件: {fixed_filename}") + # 点击目标文件 + if self.click_item_by_text(fixed_filename): + return True + else: + self.logger.error(f"点击目标文件失败: {fixed_filename}") + return False + + # 检查是否到达底部:连续两次获取的项目相同 + if current_items == previous_items and len(current_items) > 0: + self.logger.info("连续两次获取的项目相同,已到达列表底部") + break + + # 更新前一次项目集合 + previous_items = current_items.copy() + + # 向下滑动列表以加载更多项目 + if not self.scroll_list(direction="down"): + self.logger.error("向下滑动列表失败") + return False + + scroll_count += 1 + self.logger.info(f"第 {scroll_count} 次向下滑动,继续查找...") + + # 如果向下滑动未找到,尝试向上滑动查找 + self.logger.info("向下滑动未找到目标,开始向上滑动查找") + + # 重置滚动计数 + scroll_count = 0 + + while scroll_count < max_scroll_attempts: + # 向上滑动列表 + # 如果返回False,说明已经滑动到顶 + if not self.scroll_list(direction="up"): + # 检查是否是因为滑动到顶而返回False + if "已滑动到列表顶部" in self.logger.handlers[0].buffer[-1].message: + self.logger.info("已滑动到列表顶部,停止向上滑动") + break + else: + self.logger.error("向上滑动列表失败") + return False + + # 获取当前页面中的所有项目 + current_items = self.get_current_items() + # self.logger.info(f"向上滑动后找到 {len(current_items)} 个项目: {current_items}") + + # 检查目标文件是否在当前页面中 + if fixed_filename in current_items: + self.logger.info(f"找到目标文件: {fixed_filename}") + # 点击目标文件 + if self.click_item_by_text(fixed_filename): + return True + else: + self.logger.error(f"点击目标文件失败: {fixed_filename}") + return False + + scroll_count += 1 + self.logger.info(f"第 {scroll_count} 次向上滑动,继续查找...") + + self.logger.warning(f"经过 {max_scroll_attempts * 2} 次滑动仍未找到目标文件") + return False + + except TimeoutException: + self.logger.error("等待线路列表元素超时") + return False + except Exception as e: + self.logger.error(f"查找关键词时出错: {str(e)}") + return False + + def handle_measurement_dialog(self): + """处理测量弹窗 - 选择继续测量""" + try: + self.logger.info("检查测量弹窗...") + + # 直接尝试点击"继续测量"按钮 + continue_btn = WebDriverWait(self.driver, 2).until( + EC.element_to_be_clickable((AppiumBy.ID, "com.bjjw.cjgc:id/measure_continue_btn")) + ) + continue_btn.click() + self.logger.info("已点击'继续测量'按钮") + return True + + except TimeoutException: + self.logger.info("未找到继续测量按钮,可能没有弹窗") + return True # 没有弹窗也认为是成功的 + except Exception as e: + self.logger.error(f"点击继续测量按钮时出错: {str(e)}") + return False + + # 检查有没有平差处理按钮 + def check_apply_btn(self): + """检查是否有平差处理按钮""" + try: + apply_btn = WebDriverWait(self.driver, 5).until( + EC.element_to_be_clickable((AppiumBy.ID, "com.bjjw.cjgc:id/point_measure_btn")) + ) + if apply_btn.is_displayed(): + logging.info("进入平差页面") + else: + self.logger.info("没有找到'平差处理'按钮") + return True + except TimeoutException: + self.logger.info("未找到平差处理按钮") + return False # 没有弹窗也认为是成功的 + except Exception as e: + self.logger.error(f"点击平差处理按钮时出错: {str(e)}") + return False + + + def get_line_end_time(self, line_code): + """ + 从全局字典中获取线路编码对应的结束时间 + 参数: + line_code: 线路编码 + 返回: + tuple: (date_str, time_str) 日期和时间字符串 + """ + + if line_code in global_variable.LINE_TIME_MAPPING_DICT: + end_time = global_variable.LINE_TIME_MAPPING_DICT[line_code] + date_str = end_time.strftime("%Y-%m-%d") + time_str = end_time.strftime("%H:%M:%S") + return (date_str, time_str) + else: + self.logger.warning(f"未找到线路编码 {line_code} 的结束时间") + return (None, None) + + def show_line_time_mapping(self): + """ + 显示当前全局字典中的所有线路编码和时间 + """ + + self.logger.info("\n当前全局字典内容:") + if global_variable.LINE_TIME_MAPPING_DICT: + for line_code, end_time in sorted(global_variable.LINE_TIME_MAPPING_DICT.items()): + date_str = end_time.strftime("%Y-%m-%d") + time_str = end_time.strftime("%H:%M:%S") + self.logger.info(f" {line_code}: {date_str} {time_str}") + else: + self.logger.info(" 全局字典为空") + self.logger.info(f"总计: {len(global_variable.LINE_TIME_MAPPING_DICT)} 条记录\n") + def clear_line_time_mapping(self): + """ + 清空全局字典 + """ + global_variable.LINE_TIME_MAPPING_DICT.clear() + self.logger.info("已清空全局字典") + + def set_device_time(self, device_id, time_str=None, date_str=None, disable_auto_sync=True): + """ + 通过ADB设置设备时间(带管理员权限) + + 参数: + device_id: 设备ID + time_str: 时间字符串,格式 "HH:MM:SS" (例如: "14:30:00") + date_str: 日期字符串,格式 "YYYY-MM-DD" (例如: "2024-10-15") + disable_auto_sync: 是否禁用自动时间同步(防止设置的时间被网络时间覆盖) + + 返回: + bool: 操作是否成功 + """ + try: + if time_str is None and date_str is None: + return True + + # 首先尝试获取设备的root权限 + self.logger.info(f"尝试获取设备 {device_id} 的root权限...") + root_result = subprocess.run( + ["adb", "-s", device_id, "root"], + capture_output=True, + text=True, + timeout=10 + ) + + # 检查root权限获取是否成功(有些设备可能返回非0但实际已获取权限) + if root_result.returncode != 0: + self.logger.warning(f"获取root权限返回非0状态码,但继续尝试操作: {root_result.stderr.strip()}") + + now = datetime.now() + hour, minute, second = map(int, (time_str or f"{now.hour}:{now.minute}:{now.second}").split(":")) + year, month, day = map(int, (date_str or f"{now.year}-{now.month}-{now.day}").split("-")) + + # 禁用自动同步 + if disable_auto_sync: + # 使用su命令以root权限执行设置 + subprocess.run( + ["adb", "-s", device_id, "shell", "su", "-c", + "settings put global auto_time 0"], + timeout=5 + ) + subprocess.run( + ["adb", "-s", device_id, "shell", "su", "-c", + "settings put global auto_time_zone 0"], + timeout=5 + ) + + # 优先尝试旧格式 (MMDDhhmmYYYY.ss) + adb_time_str_old = f"{month:02d}{day:02d}{hour:02d}{minute:02d}{year:04d}.{second:02d}" + cmd_old = [ + "adb", "-s", device_id, "shell", "su", "-c", + f"date {adb_time_str_old}" + ] + result = subprocess.run(cmd_old, capture_output=True, text=True, timeout=10) + + if result.returncode != 0: + self.logger.warning(f"旧格式失败,尝试新格式设置日期时间") + + # 尝试新格式(Toybox),使用su -c确保以root权限执行 + adb_time_str_new = f"{year:04d}{month:02d}{day:02d}.{hour:02d}{minute:02d}{second:02d}" + cmd_new = [ + "adb", "-s", device_id, "shell", "su", "-c", + f"date {adb_time_str_new}" + ] + result = subprocess.run(cmd_new, capture_output=True, text=True, timeout=10) + + if result.returncode == 0: + self.logger.info(f"设备 {device_id} 时间设置成功: {year}-{month}-{day} {hour}:{minute}:{second}") + return True + else: + self.logger.error(f"设备 {device_id} 设置时间失败: {result.stderr.strip()}") + return False + + except subprocess.TimeoutExpired: + self.logger.error(f"设备 {device_id} 设置时间命令执行超时") + return False + except Exception as e: + self.logger.error(f"设备 {device_id} 设置时间时发生异常: {str(e)}") + return False + + def disable_wifi(self, device_id): + """ + 通过ADB关闭设备WiFi + + 返回: + bool: 操作是否成功 + """ + try: + # 关闭WiFi + cmd_disable_wifi = [ + "adb", "-s", device_id, + "shell", "svc", "wifi", "disable" + ] + + result = subprocess.run(cmd_disable_wifi, capture_output=True, text=True, timeout=10) + + if result.returncode == 0: + self.logger.info(f"设备 {device_id} WiFi已关闭") + time.sleep(1) # 等待WiFi完全关闭 + return True + else: + self.logger.error(f"设备 {device_id} 关闭WiFi失败: {result.stderr}") + return False + + except subprocess.TimeoutExpired: + self.logger.error(f"设备 {device_id} 关闭WiFi命令执行超时") + return False + except Exception as e: + self.logger.error(f"设备 {device_id} 关闭WiFi时发生错误: {str(e)}") + return False + + def enable_wifi(self, device_id): + """ + 通过ADB打开设备WiFi + + 返回: + bool: 操作是否成功 + """ + try: + # 打开WiFi + cmd_enable_wifi = [ + "adb", "-s", device_id, + "shell", "svc", "wifi", "enable" + ] + + result = subprocess.run(cmd_enable_wifi, capture_output=True, text=True, timeout=10) + + if result.returncode == 0: + self.logger.info(f"设备 {device_id} WiFi已打开") + time.sleep(3) # 等待WiFi完全连接 + return True + else: + self.logger.error(f"设备 {device_id} 打开WiFi失败: {result.stderr}") + return False + + except subprocess.TimeoutExpired: + self.logger.error(f"设备 {device_id} 打开WiFi命令执行超时") + return False + except Exception as e: + self.logger.error(f"设备 {device_id} 打开WiFi时发生错误: {str(e)}") + return False + + def get_current_time(self, device_id): + """ + 获取设备当前时间 + + 返回: + str: 设备当前时间字符串,如果获取失败则返回None + """ + try: + cmd_get_time = [ + "adb", "-s", device_id, + "shell", "date" + ] + + result = subprocess.run(cmd_get_time, capture_output=True, text=True, timeout=10) + + if result.returncode == 0: + current_time = result.stdout.strip() + self.logger.info(f"设备 {device_id} 当前时间: {current_time}") + return current_time + else: + self.logger.error(f"设备 {device_id} 获取时间失败: {result.stderr}") + return None + + except subprocess.TimeoutExpired: + self.logger.error(f"设备 {device_id} 获取时间命令执行超时") + return None + except Exception as e: + self.logger.error(f"设备 {device_id} 获取时间时发生错误: {str(e)}") + return None + + def check_wifi_status(self, device_id): + """ + 检查设备WiFi状态 + + 返回: + str: "enabled"表示已开启, "disabled"表示已关闭, None表示获取失败 + """ + try: + cmd_check_wifi = [ + "adb", "-s", device_id, + "shell", "dumpsys", "wifi", "|", "grep", "Wi-Fi" + ] + + result = subprocess.run(cmd_check_wifi, capture_output=True, text=True, timeout=10) + + if result.returncode == 0: + wifi_status = result.stdout.strip() + if "enabled" in wifi_status.lower(): + self.logger.info(f"设备 {device_id} WiFi状态: 已开启") + return "enabled" + elif "disabled" in wifi_status.lower(): + self.logger.info(f"设备 {device_id} WiFi状态: 已关闭") + return "disabled" + else: + self.logger.warning(f"设备 {device_id} 无法确定WiFi状态: {wifi_status}") + return None + else: + # 尝试另一种方法检查WiFi状态 + cmd_check_wifi_alt = [ + "adb", "-s", device_id, + "shell", "settings", "get", "global", "wifi_on" + ] + + result_alt = subprocess.run(cmd_check_wifi_alt, capture_output=True, text=True, timeout=10) + + if result_alt.returncode == 0: + wifi_on = result_alt.stdout.strip() + if wifi_on == "1": + self.logger.info(f"设备 {device_id} WiFi状态: 已开启") + return "enabled" + elif wifi_on == "0": + self.logger.info(f"设备 {device_id} WiFi状态: 已关闭") + return "disabled" + else: + self.logger.warning(f"设备 {device_id} 无法确定WiFi状态: {wifi_on}") + return None + else: + self.logger.error(f"设备 {device_id} 检查WiFi状态失败: {result_alt.stderr}") + return None + + except subprocess.TimeoutExpired: + self.logger.error(f"设备 {device_id} 检查WiFi状态命令执行超时") + return None + except Exception as e: + self.logger.error(f"设备 {device_id} 检查WiFi状态时发生错误: {str(e)}") + return None + + def take_screenshot(self, filename_prefix="screenshot"): + """ + 通过Appium驱动截取设备屏幕 + + 参数: + filename_prefix: 截图文件前缀 + + 返回: + bool: 操作是否成功 + """ + try: + # 创建测试结果目录 + screenshots_dir = os.path.join(os.path.dirname(os.path.abspath(__file__)), '../test_results/screenshots') + if not os.path.exists(screenshots_dir): + os.makedirs(screenshots_dir) + self.logger.info(f"创建截图目录: {screenshots_dir}") + + # 截图保存 + screenshot_file = os.path.join( + screenshots_dir, + f"{filename_prefix}_{datetime.now().strftime('%Y%m%d')}.png" + ) + self.driver.save_screenshot(screenshot_file) + self.logger.info(f"截图已保存: {screenshot_file}") + return True + + except Exception as e: + self.logger.error(f"截图时发生错误: {str(e)}") + return False + + def wait_for_measurement_end(self, timeout=900): + """ + 等待按钮变成"测量结束",最多15分钟,包含驱动重新初始化机制 + + Args: + timeout: 超时时间,默认900秒(15分钟) + + Returns: + bool: 是否成功等到测量结束按钮 + """ + try: + # 更新WebDriverWait等待时间为900秒 + self.wait = WebDriverWait(self.driver, 900) + self.logger.info(f"设备等待测量结束按钮出现,最多等待 {timeout} 秒") + + start_time = time.time() + reinit_attempts = 0 + max_reinit_attempts = 3 # 最大重新初始化次数 + + while time.time() - start_time < timeout: + try: + # 使用XPath查找文本为"测量结束"的按钮 + measurement_end_button = self.driver.find_element( + AppiumBy.XPATH, + "//android.widget.Button[@text='测量结束']" + ) + + if measurement_end_button.is_displayed() and measurement_end_button.is_enabled(): + self.logger.info(f"设备检测到测量结束按钮") + return True + + except NoSuchElementException: + # 按钮未找到,继续等待 + pass + except Exception as e: + error_msg = str(e) + self.logger.warning(f"设备查找测量结束按钮时出现异常: {error_msg}") + + # 检测是否是UiAutomator2服务崩溃 + if 'UiAutomator2 server' in error_msg and 'instrumentation process is not running' in error_msg and reinit_attempts < max_reinit_attempts: + reinit_attempts += 1 + self.logger.info(f"设备检测到UiAutomator2服务崩溃,尝试第 {reinit_attempts} 次重新初始化驱动") + + # 尝试重新初始化驱动 + if self._reinit_driver(): + self.logger.info(f"设备驱动重新初始化成功") + else: + self.logger.error(f"设备驱动重新初始化失败") + # 继续尝试,而不是立即失败 + + # 等待一段时间后再次检查 + time.sleep(3) + + # 每30秒输出一次等待状态 + if int(time.time() - start_time) % 30 == 0: + elapsed = int(time.time() - start_time) + self.logger.info(f"设备 {self.device_id} 已等待 {elapsed} 秒,仍在等待测量结束...") + + self.logger.error(f"设备 {self.device_id} 等待测量结束按钮超时") + return False + + except Exception as e: + self.logger.error(f"设备 {self.device_id} 等待测量结束时发生错误: {str(e)}") + return False + + def _reinit_driver(self): + """ + 重新初始化Appium驱动 + + Returns: + bool: 是否成功重新初始化 + """ + try: + # 首先尝试关闭现有的驱动 + if hasattr(self, 'driver') and self.driver: + try: + self.driver.quit() + except: + self.logger.warning("关闭现有驱动时出现异常") + + # 导入必要的模块 + from appium import webdriver + from appium.options.android import UiAutomator2Options + + # 重新创建驱动配置 + options = UiAutomator2Options() + options.platform_name = "Android" + options.device_name = self.device_id + options.app_package = "com.bjjw.cjgc" + options.app_activity = ".activity.LoginActivity" + options.automation_name = "UiAutomator2" + options.no_reset = True + options.auto_grant_permissions = True + options.new_command_timeout = 300 + options.udid = self.device_id + + # 重新连接驱动 + self.logger.info(f"正在重新初始化设备 {self.device_id} 的驱动...") + self.driver = webdriver.Remote("http://localhost:4723", options=options) + + # 重新初始化等待对象 + from selenium.webdriver.support.ui import WebDriverWait + self.wait = WebDriverWait(self.driver, 1) + + self.logger.info(f"设备 {self.device_id} 驱动重新初始化完成") + return True + + except Exception as e: + self.logger.error(f"设备 {self.device_id} 驱动重新初始化失败: {str(e)}") + return False + + def handle_confirmation_dialog(self, device_id, timeout=2): + """ + 处理确认弹窗,点击"是"按钮 + + Args: + device_id: 设备ID + timeout: 等待弹窗的超时时间 + + Returns: + bool: 是否成功处理弹窗 + """ + # 等待弹窗出现(最多等待2秒) + try: + dialog_message = WebDriverWait(self.driver, timeout).until( + EC.presence_of_element_located((AppiumBy.XPATH, "//android.widget.TextView[@text='是否退出测量界面?']")) + ) + + self.logger.info(f"设备 {device_id} 检测到确认弹窗") + + # 查找并点击"是"按钮 + confirm_button = self.driver.find_element( + AppiumBy.XPATH, + "//android.widget.Button[@text='是' and @resource-id='android:id/button1']" + ) + + if confirm_button.is_displayed() and confirm_button.is_enabled(): + self.logger.info(f"设备 {device_id} 点击确认弹窗的'是'按钮") + confirm_button.click() + time.sleep(0.5) + return True + else: + self.logger.error(f"设备 {device_id} '是'按钮不可点击") + return False + + except TimeoutException: + # 超时未找到弹窗,认为没有弹窗,返回成功 + self.logger.info(f"设备 {device_id} 等待 {timeout} 秒未发现确认弹窗,可能没有弹窗,返回成功") + return True + + def click_back_button(self, device_id): + """点击手机系统返回按钮""" + try: + self.driver.back() + self.logger.info("已点击手机系统返回按钮") + return True + except Exception as e: + self.logger.error(f"点击手机系统返回按钮失败: {str(e)}") + return False + + def handle_back_button_with_confirmation(self, device_id, timeout=10): + """ + 处理返回按钮的确认弹窗 + + Args: + device_id: 设备ID + timeout: 等待弹窗的超时时间 + + Returns: + bool: 是否成功处理返回确认弹窗 + """ + logging.info(f"进入handle_back_button_with_confirmation函数") + try: + self.logger.info(f"设备 {device_id} 等待返回确认弹窗出现") + + start_time = time.time() + while time.time() - start_time < timeout: + try: + # 检查是否存在确认弹窗 - 使用多种定位策略提高兼容性 + dialog_selectors = [ + "//android.widget.TextView[@text='是否退出测量界面?']", + "//android.widget.TextView[contains(@text, '退出测量界面')]", + "//android.widget.TextView[contains(@text, '是否退出')]" + ] + + dialog_message = None + for selector in dialog_selectors: + try: + dialog_message = self.driver.find_element(AppiumBy.XPATH, selector) + if dialog_message.is_displayed(): + break + except NoSuchElementException: + continue + + if dialog_message and dialog_message.is_displayed(): + self.logger.info(f"设备 {device_id} 检测到返回确认弹窗") + + # 查找并点击"是"按钮 - 使用多种定位策略 + confirm_selectors = [ + "//android.widget.Button[@text='是' and @resource-id='android:id/button1']", + "//android.widget.Button[@text='是']", + "//android.widget.Button[@resource-id='android:id/button1']", + "//android.widget.Button[contains(@text, '是')]" + ] + + confirm_button = None + for selector in confirm_selectors: + try: + confirm_button = self.driver.find_element(AppiumBy.XPATH, selector) + if confirm_button.is_displayed() and confirm_button.is_enabled(): + break + except NoSuchElementException: + continue + + if confirm_button and confirm_button.is_displayed() and confirm_button.is_enabled(): + self.logger.info(f"设备 {device_id} 点击确认弹窗的'是'按钮") + confirm_button.click() + time.sleep(1) + + # 验证弹窗是否消失 + try: + self.driver.find_element(AppiumBy.XPATH, "//android.widget.TextView[@text='是否退出测量界面?']") + self.logger.warning(f"设备 {device_id} 确认弹窗可能未正确关闭") + return False + except NoSuchElementException: + self.logger.info(f"设备 {device_id} 确认弹窗已成功关闭") + return True + else: + self.logger.error(f"设备 {device_id} 未找到可点击的'是'按钮") + return False + + except NoSuchElementException: + # 弹窗未找到,继续等待 + pass + except Exception as e: + self.logger.warning(f"设备 {device_id} 查找确认弹窗时出现异常: {str(e)}") + + time.sleep(1) + + self.logger.error(f"设备 {device_id} 等待返回确认弹窗超时") + return False + + except Exception as e: + self.logger.error(f"设备 {device_id} 处理返回确认弹窗时发生错误: {str(e)}") + return False + + def handle_adjustment_result_dialog(self): + """处理平差结果确认弹窗""" + try: + self.logger.info("开始检测平差结果弹窗") + + # 等待弹窗出现(最多等待5秒) + warning_dialog = WebDriverWait(self.driver, 5).until( + EC.presence_of_element_located((AppiumBy.ID, "android:id/parentPanel")) + ) + + # 验证弹窗内容 + alert_title = warning_dialog.find_element(AppiumBy.ID, "android:id/alertTitle") + alert_message = warning_dialog.find_element(AppiumBy.ID, "android:id/message") + + self.logger.info(f"检测到弹窗 - 标题: {alert_title.text}, 消息: {alert_message.text}") + + # 确认是目标弹窗 + if "警告" in alert_title.text and "是否保留测量成果" in alert_message.text: + self.logger.info("确认是平差结果确认弹窗") + + # 点击"是 保留成果"按钮 + yes_button = warning_dialog.find_element(AppiumBy.ID, "android:id/button1") + if yes_button.text == "是 保留成果": + yes_button.click() + self.logger.info("已点击'是 保留成果'按钮") + + # 等待弹窗消失 + WebDriverWait(self.driver, 5).until( + EC.invisibility_of_element_located((AppiumBy.ID, "android:id/parentPanel")) + ) + self.logger.info("弹窗已关闭") + return True + else: + self.logger.error(f"按钮文本不匹配,期望'是 保留成果',实际: {yes_button.text}") + return False + else: + self.logger.warning("弹窗内容不匹配,不是目标弹窗") + return False + + except TimeoutException: + self.logger.info("未检测到平差结果弹窗,继续流程") + return True # 没有弹窗也是正常情况 + except Exception as e: + self.logger.error(f"处理平差结果弹窗时出错: {str(e)}") + return False + + def check_measurement_list(self, device_id): + """ + 检查是否存在测量列表 + + Args: + device_id: 设备ID + + Returns: + bool: 如果不存在测量列表返回True,存在返回False + """ + try: + # 等待线路列表容器出现 + self.wait.until( + EC.presence_of_element_located((AppiumBy.ID, ids.MEASURE_LIST_ID)) + ) + self.logger.info("线路列表容器已找到") + + # 如果存在MEASURE_LIST_ID,说明有测量列表,不需要执行后续步骤 + self.logger.info(f"设备 {device_id} 存在测量列表,无需执行后续返回操作") + return False + + except TimeoutException: + # 等待超时,说明没有测量列表 + self.logger.info(f"设备 {device_id} 未找到测量列表,可以继续执行后续步骤") + return True + except Exception as e: + self.logger.error(f"设备 {device_id} 检查测量列表时发生错误: {str(e)}") + return True + + def handle_back_navigation(self, breakpoint_name, device_id): + """ + 完整的返回导航处理流程 + + Args: + breakpoint_name: 断点名称 + device_id: 设备ID + + Returns: + bool: 整个返回导航流程是否成功 + """ + try: + # time.sleep(2) + self.logger.info(f"已点击平差处理按钮,检查是否在测量页面") + + # 检测是否存在测量列表(修正逻辑) + has_measurement_list = self.check_measurement_list(device_id) + if not has_measurement_list: + self.logger.info(f"设备 {device_id} 存在测量列表,重新执行平差流程") + + # 把断点名称给find_keyword + if not self.find_keyword(breakpoint_name): + self.logger.error(f"设备 {device_id} 未找到包含 {breakpoint_name} 的文件名") + return False + + if not self.handle_measurement_dialog(): + self.logger.error(f"设备 {device_id} 处理测量弹窗失败") + return False + + if not self.check_apply_btn(): + self.logger.error(f"设备 {device_id} 检查平差处理按钮失败") + return False + + + # 4. 点击平差处理按钮 + if not self.click_adjustment_button(device_id): + self.logger.error(f"设备 {device_id} 点击平差处理按钮失败") + return False + + self.logger.info(f"重新选择断点并点击平差处理按钮成功") + return True + + else: + self.logger.info(f"不在测量页面,继续执行后续返回操作") + return True + + except Exception as e: + self.logger.error(f"设备 {device_id} 处理返回导航时发生错误: {str(e)}") + return False + + def execute_back_navigation_steps(self, device_id): + """ + 执行实际的返回导航步骤 + + Args: + device_id: 设备ID + + Returns: + bool: 导航是否成功 + """ + try: + # 1. 首先点击返回按钮 + if not self.click_back_button(device_id): + self.logger.error(f"设备 {device_id} 点击返回按钮失败") + return False + + # 2. 处理返回确认弹窗 + self.logger.info(f"已点击返回按钮,等待处理返回确认弹窗") + if not self.handle_confirmation_dialog(device_id): + self.logger.error(f"设备 {device_id} 处理返回确认弹窗失败") + return False + + + # 3. 验证是否成功返回到上一页面 + time.sleep(1) # 等待页面跳转完成 + + # 可以添加页面验证逻辑,比如检查是否返回到预期的页面 + # 这里可以根据实际应用添加特定的页面元素验证 + + self.logger.info(f"设备 {device_id} 返回导航流程完成") + return True + + except Exception as e: + self.logger.error(f"设备 {device_id} 执行返回导航步骤时发生错误: {str(e)}") + return False + + def scroll_to_bottom_and_screenshot(self, device_id): + """ + 检测到测量结束后,下滑列表到最底端,点击最后一个spinner,再下滑一次,点击平差处理按钮后截图 + + Args: + device_id: 设备ID + + Returns: + bool: 操作是否成功 + """ + try: + self.logger.info(f"设备 {device_id} 开始执行测量结束后的操作流程") + time.sleep(5) + + # 1. 下滑列表到最底端 + if not self.scroll_list_to_bottom(device_id): + self.logger.error(f"设备 {device_id} 下滑列表到底部失败") + return False + + # 2. 点击最后一个spinner + if not self.click_last_spinner_with_retry(device_id): + self.logger.error(f"设备 {device_id} 点击最后一个spinner失败") + return False + + # 3. 再下滑一次 + if not self.scroll_down_once(device_id): + self.logger.warning(f"设备 {device_id} 再次下滑失败,但继续执行") + + # 4. 点击平差处理按钮 + if not self.click_adjustment_button(device_id): + self.logger.error(f"设备 {device_id} 点击平差处理按钮失败") + return False + + # # 5. 在点击平差处理按钮后截图 + # time.sleep(2) # 等待平差处理按钮点击后的界面变化 + # if not self.take_screenshot("after_adjustment_button_click"): + # self.logger.error(f"设备 {device_id} 截图失败") + # return False + + self.logger.info(f"设备 {device_id} 测量结束后操作流程完成") + return True + + except Exception as e: + self.logger.error(f"设备 {device_id} 执行测量结束后操作时发生错误: {str(e)}") + return False + + def scroll_list_to_bottom(self, device_id, max_swipes=60): + """ + 下滑列表到最底端 + + Args: + device_id: 设备ID + max_swipes: 最大下滑次数 + + Returns: + bool: 是否滑动到底部 + """ + try: + self.logger.info(f"设备 {device_id} 开始下滑列表到底部") + + # 获取列表元素 + list_view = self.driver.find_element(AppiumBy.ID, "com.bjjw.cjgc:id/auto_data_list") + + same_content_count = 0 + + # 初始化第一次的子元素文本 + initial_child_elements = list_view.find_elements(AppiumBy.CLASS_NAME, "android.widget.TextView") + current_child_texts = "|".join([ + elem.text.strip() for elem in initial_child_elements + if elem.text and elem.text.strip() + ]) + + for i in range(max_swipes): + # 执行下滑操作 + self.driver.execute_script("mobile: scrollGesture", { + 'elementId': list_view.id, + 'direction': 'down', + 'percent': 0.8, + 'duration': 500 + }) + + time.sleep(0.5) + + # 获取滑动后的子元素文本 + new_child_elements = list_view.find_elements(AppiumBy.CLASS_NAME, "android.widget.TextView") + new_child_texts = "|".join([ + elem.text.strip() for elem in new_child_elements + if elem.text and elem.text.strip() + ]) + + # 判断内容是否变化:若连续3次相同,认为到达底部 + if new_child_texts == current_child_texts: + same_content_count += 1 + if same_content_count >= 2: + self.logger.info(f"设备 {device_id} 列表已滑动到底部,共滑动 {i+1} 次") + return True + else: + same_content_count = 0 # 内容变化,重置计数 + current_child_texts = new_child_texts # 更新上一次内容 + + self.logger.debug(f"设备 {device_id} 第 {i+1} 次下滑完成,当前子元素文本: {new_child_texts[:50]}...") # 打印部分文本 + + self.logger.warning(f"设备 {device_id} 达到最大下滑次数 {max_swipes},可能未完全到底部") + return True + + except Exception as e: + self.logger.error(f"设备 {device_id} 下滑列表时发生错误: {str(e)}") + return False + + def click_last_spinner_with_retry(self, device_id, max_retries=2): + """带重试机制的点击方法""" + for attempt in range(max_retries): + try: + if self.click_last_spinner(device_id): + return True + self.logger.warning(f"设备 {device_id} 第{attempt + 1}次点击失败,准备重试") + time.sleep(1) # 重试前等待 + except Exception as e: + self.logger.error(f"设备 {device_id} 第{attempt + 1}次尝试失败: {str(e)}") + + self.logger.error(f"设备 {device_id} 所有重试次数已用尽") + return False + + def click_last_spinner(self, device_id): + """ + 点击最后一个spinner + + Args: + device_id: 设备ID + + Returns: + bool: 是否成功点击 + """ + try: + self.logger.info(f"设备 {device_id} 查找最后一个spinner") + + # 查找所有的spinner元素 + spinners = self.driver.find_elements(AppiumBy.ID, "com.bjjw.cjgc:id/spinner") + + if not spinners: + self.logger.error(f"设备 {device_id} 未找到任何spinner元素") + return False + + # 获取最后一个spinner + last_spinner = spinners[-1] + + if not (last_spinner.is_displayed() and last_spinner.is_enabled()): + self.logger.error(f"设备 {device_id} 最后一个spinner不可点击") + return False + + # 点击操作 + self.logger.info(f"设备 {device_id} 点击最后一个spinner") + last_spinner.click() + + # 执行额外一次下滑操作 + self.scroll_down_once(device_id) + + max_retries = 3 # 最大重试次数 + retry_count = 0 + wait_timeout = 5 # 增加等待时间到5秒 + + while retry_count < max_retries: + try: + # 确保device_id正确设置,使用全局变量作为备用 + if not hasattr(self, 'device_id') or not self.device_id: + # 优先使用传入的device_id,其次使用全局变量 + self.device_id = device_id if device_id else global_variable.GLOBAL_DEVICE_ID + + # 使用self.device_id,确保有默认值 + actual_device_id = self.device_id if self.device_id else global_variable.GLOBAL_DEVICE_ID + + if not check_session_valid(self.driver, actual_device_id): + self.logger.warning(f"设备 {actual_device_id} 会话无效,尝试重新连接驱动...") + try: + # 使用正确的设备ID进行重连 + new_driver, new_wait = reconnect_driver(actual_device_id, self.driver) + if new_driver: + self.driver = new_driver + self.wait = new_wait + self.logger.info(f"设备 {actual_device_id} 驱动重连成功") + else: + self.logger.error(f"设备 {actual_device_id} 驱动重连失败") + retry_count += 1 + continue + except Exception as e: + self.logger.error(f"设备 {actual_device_id} 驱动重连异常: {str(e)}") + retry_count += 1 + continue + + # 点击spinner(如果是重试,需要重新获取元素) + if retry_count > 0: + spinners = self.driver.find_elements(AppiumBy.CLASS_NAME, "android.widget.Spinner") + if not spinners: + self.logger.error(f"设备 {device_id} 未找到spinner元素") + retry_count += 1 + continue + last_spinner = spinners[-1] + if not (last_spinner.is_displayed() and last_spinner.is_enabled()): + self.logger.error(f"设备 {device_id} spinner不可点击") + retry_count += 1 + continue + self.logger.info(f"设备 {device_id} 重新点击spinner") + last_spinner.click() + # 重试时也执行下滑操作 + self.scroll_down_once(device_id) + + # 等待下拉菜单出现,增加等待时间到5秒 + wait = WebDriverWait(self.driver, wait_timeout) + detail_show = wait.until( + EC.presence_of_element_located((AppiumBy.ID, "com.bjjw.cjgc:id/detailshow")) + ) + + if detail_show.is_displayed(): + self.logger.info(f"设备 {device_id} spinner点击成功,下拉菜单已展开") + return True + else: + self.logger.error(f"设备 {device_id} 下拉菜单未显示") + retry_count += 1 + continue + + except Exception as wait_error: + error_msg = str(wait_error) + self.logger.error(f"设备 {device_id} 等待下拉菜单超时 (第{retry_count+1}次尝试): {error_msg}") + + # 检查是否是连接断开相关的错误 + if not check_session_valid(self.driver, self.device_id): + self.logger.warning(f"设备 {self.device_id} 会话无效,尝试重新连接驱动...") + # if any(keyword in error_msg for keyword in ['socket hang up', 'Could not proxy command']): + # self.logger.warning(f"设备 {device_id} 检测到连接相关错误,尝试重连...") + if not reconnect_driver(self.device_id, self.driver): + self.logger.error(f"设备 {device_id} 驱动重连失败") + + retry_count += 1 + if retry_count < max_retries: + self.logger.info(f"设备 {device_id} 将在1秒后进行第{retry_count+1}次重试") + time.sleep(1) # 等待1秒后重试 + + self.logger.error(f"设备 {device_id} 经过{max_retries}次重试后仍无法展开下拉菜单") + return False + + except Exception as e: + self.logger.error(f"设备 {device_id} 点击最后一个spinner时发生错误: {str(e)}") + return False + + def scroll_down_once(self, device_id): + """ + 再次下滑一次 + + Args: + device_id: 设备ID + + Returns: + bool: 是否成功下滑 + """ + try: + self.logger.info(f"设备 {device_id} 执行额外一次下滑") + + # 获取列表元素 + list_view = self.driver.find_element(AppiumBy.ID, "com.bjjw.cjgc:id/auto_data_list") + + # 执行下滑操作 + self.driver.execute_script("mobile: scrollGesture", { + 'elementId': list_view.id, + 'direction': 'down', + 'percent': 0.5 + }) + + time.sleep(1) + self.logger.info(f"设备 {device_id} 额外下滑完成") + return True + + except Exception as e: + self.logger.error(f"设备 {device_id} 额外下滑时发生错误: {str(e)}") + return False + + def click_adjustment_button(self, device_id): + """ + 点击平差处理按钮 + + Args: + device_id: 设备ID + + Returns: + bool: 是否成功点击 + """ + try: + self.logger.info(f"设备 {device_id} 查找平差处理按钮") + + # 查找平差处理按钮 + adjustment_button = self.driver.find_element(AppiumBy.ID, "com.bjjw.cjgc:id/point_measure_btn") + + # 验证按钮文本 + button_text = adjustment_button.text + if "平差处理" not in button_text: + self.logger.warning(f"设备 {device_id} 按钮文本不匹配,期望'平差处理',实际: {button_text}") + + if adjustment_button.is_displayed() and adjustment_button.is_enabled(): + self.logger.info(f"设备 {device_id} 点击平差处理按钮") + adjustment_button.click() + time.sleep(3) # 等待平差处理完成 + return True + else: + self.logger.error(f"设备 {device_id} 平差处理按钮不可点击") + return False + + except NoSuchElementException: + self.logger.error(f"设备 {device_id} 未找到平差处理按钮") + return False + except Exception as e: + self.logger.error(f"设备 {device_id} 点击平差处理按钮时发生错误: {str(e)}") + return False + + def add_breakpoint_to_upload_list(self, breakpoint_name, line_num): + """添加平差完成的断点到上传列表和字典""" + if breakpoint_name and breakpoint_name not in global_variable.GLOBAL_UPLOAD_BREAKPOINT_LIST: + global_variable.GLOBAL_UPLOAD_BREAKPOINT_LIST.append(breakpoint_name) + global_variable.GLOBAL_UPLOAD_BREAKPOINT_DICT[breakpoint_name] = { + 'breakpoint_name': breakpoint_name, + 'line_num': line_num + } + + logging.info(f"成功添加断点 '{breakpoint_name}' 到上传列表") + logging.info(f"断点详细信息: 线路编码={line_num}") + return True + else: + logging.warning(f"断点名为空或已存在于列表中") + return False + + + def handle_confirmation_dialog_save(self, device_id, timeout=2): + """ + 处理确认弹窗,点击"是"按钮 + + Args: + device_id: 设备ID + timeout: 等待弹窗的超时时间 + + Returns: + bool: 是否成功处理弹窗 + """ + # 等待弹窗出现(最多等待2秒) + try: + dialog_message = WebDriverWait(self.driver, timeout).until( + EC.presence_of_element_located((AppiumBy.ID, "android:id/content")) + ) + + self.logger.info(f"设备 {device_id} 检测到确认弹窗") + + # 查找并点击"是"按钮 + confirm_button = self.driver.find_element( + AppiumBy.ID, "android:id/button1" + ) + + if confirm_button.is_displayed() and confirm_button.is_enabled(): + self.logger.info(f"设备 {device_id} 点击确认弹窗的'是'按钮") + confirm_button.click() + time.sleep(0.5) + return True + else: + self.logger.error(f"设备 {device_id} '是'按钮不可点击") + return False + + except TimeoutException: + # 超时未找到弹窗,认为没有弹窗,返回成功 + self.logger.info(f"设备 {device_id} 等待 {timeout} 秒未发现确认弹窗,可能没有弹窗,返回成功") + return True + + + def screenshot_page_manager(self, device_id): + """执行截图页面管理操作""" + max_retries = 3 + retry_count = 0 + + while retry_count < max_retries: + try: + # 检查Appium是否运行,如果没有运行则重启 + if not driver_utils.is_appium_running(4723): + self.logger.warning("Appium服务器未运行,尝试重启...") + if not driver_utils.restart_appium_server(4723): + self.logger.error("重启Appium服务器失败") + return False + # 重新初始化driver + if not reconnect_driver(device_id): + self.logger.error("重新初始化driver失败") + return False + + # 检查GLOBAL_UPLOAD_BREAKPOINT_DICT是否为空,如果为空则初始化一些测试数据 + if not global_variable.GLOBAL_UPLOAD_BREAKPOINT_DICT: + self.logger.warning("global_variable.GLOBAL_UPLOAD_BREAKPOINT_DICT为空,正在初始化测试数据") + global_variable.GLOBAL_UPLOAD_BREAKPOINT_DICT = {'CDWZQ-2标-龙骨湾右线大桥-0-7号墩-平原': 'L156372', 'CDWZQ-2标-蓝家湾特大 桥-31-31-平原': 'L159206'} + + # 创建断点列表的副本,用于重试时重新处理 + breakpoint_names = list(global_variable.GLOBAL_UPLOAD_BREAKPOINT_DICT.keys()) + processed_breakpoints = [] + + # 开始循环处理断点 + for breakpoint_name in breakpoint_names: + if breakpoint_name in processed_breakpoints: + continue + line_code = global_variable.GLOBAL_UPLOAD_BREAKPOINT_DICT[breakpoint_name] + + self.logger.info(f"开始处理要平差的断点 {breakpoint_name}") + + # 把断点名称给find_keyword + if not self.find_keyword(breakpoint_name): + self.logger.error(f"设备 {device_id} 未找到包含 {breakpoint_name} 的文件名") + continue # 继续处理下一个断点 + + if not self.handle_measurement_dialog(): + self.logger.error(f"设备 {device_id} 处理测量弹窗失败") + continue + + if not self.check_apply_btn(): + self.logger.error(f"设备 {device_id} 检查平差处理按钮失败") + self.execute_back_navigation_steps(device_id) + continue + + # 4. 点击平差处理按钮 + if not self.click_adjustment_button(device_id): + self.logger.error(f"设备 {device_id} 点击平差处理按钮失败") + continue + + # 检查是否在测量页面,在就重新执行选择断点,滑动列表到底部,点击最后一个spinner, 再下滑一次,点击平差处理按钮平差 + if not self.handle_back_navigation(breakpoint_name, device_id): + self.logger.error(f"{breakpoint_name}平差失败,未截图") + continue + + + # 检测并处理"是 保留成果"弹窗 + if not self.handle_adjustment_result_dialog(): + self.logger.error("处理平差结果弹窗失败") + continue + + # # 平差完成,将断点数据保存到上传列表中 + # if not self.add_breakpoint_to_upload_list(breakpoint_name, line_code): + # self.logger.error(f"设备 {device_id} 保存断点 {breakpoint_name} 到上传列表失败") + # continue + + # # 检查是否在测量页面,在就重新执行选择断点,滑动列表到底部,点击最后一个spinner, 再下滑一次,点击平差处理按钮平差 + # if not self.handle_back_navigation(breakpoint_name, device_id): + # self.logger.error(f"{breakpoint_name}平差失败,未截图") + # continue + + # # 检测并处理"是 保留成果"弹窗 + # if not self.handle_adjustment_result_dialog(): + # self.logger.error("处理平差结果弹窗失败") + # continue + + + # 点击返回按钮并处理弹窗 + if not self.execute_back_navigation_steps(device_id): + self.logger.error(f"设备 {device_id} 处理返回按钮确认失败") + continue + + # 成功处理完一个断点,添加到已处理列表 + processed_breakpoints.append(breakpoint_name) + self.logger.info(f"成功处理断点: {breakpoint_name}") + + # 检查是否所有断点都处理完成 + if len(processed_breakpoints) == len(breakpoint_names): + self.logger.info(f"设备 {device_id} 平差页面操作执行完成") + return True + else: + self.logger.warning(f"设备 {device_id} 部分断点处理失败,已成功处理 {len(processed_breakpoints)}/{len(breakpoint_names)} 个断点") + return True + + except Exception as e: + retry_count += 1 + self.logger.error(f"设备 {device_id} 执行截图页面操作时出错 (重试 {retry_count}/{max_retries}): {str(e)}") + + # 检查是否为连接错误 + if driver_utils.check_connection_error(e): + self.logger.warning("检测到连接错误,尝试重启Appium服务器...") + if not driver_utils.restart_appium_server(4723): + self.logger.error("重启Appium服务器失败") + else: + self.logger.info("Appium服务器重启成功,等待重新连接...") + time.sleep(10) + + # 重新初始化driver + if not reconnect_driver(device_id): + self.logger.error("重新初始化driver失败") + if retry_count >= max_retries: + break + continue + + + if retry_count >= max_retries: + self.logger.error(f"设备 {device_id} 达到最大重试次数,停止执行") + break + + self.logger.info(f"等待10秒后重试...") + time.sleep(10) + + return False + def run_automation_test(self): + # 滑动列表到底部 + if not self.scroll_list_to_bottom(self.device_id): + self.logger.error(f"设备 {self.device_id} 下滑列表到底部失败") + return False + + # 2. 点击最后一个spinner + if not self.click_last_spinner_with_retry(self.device_id): + self.logger.error(f"设备 {self.device_id} 点击最后一个spinner失败") + return False + + # 3. 再下滑一次 + if not self.scroll_down_once(self.device_id): + self.logger.warning(f"设备 {self.device_id} 再次下滑失败,但继续执行") + + # 4. 点击平差处理按钮 + if not self.click_adjustment_button(self.device_id): + self.logger.error(f"设备 {self.device_id} 点击平差处理按钮失败") + return False diff --git a/page_objects/upload_config_page.py b/page_objects/upload_config_page.py new file mode 100644 index 0000000..1ac8ef0 --- /dev/null +++ b/page_objects/upload_config_page.py @@ -0,0 +1,1875 @@ +#上传配置页面 +# \page_objects\test_upload_config_page.py +from appium.webdriver.common.appiumby import AppiumBy +from selenium.webdriver.support.ui import WebDriverWait +from selenium.webdriver.support import expected_conditions as EC +from selenium.common.exceptions import TimeoutException, NoSuchElementException +import logging +import time +import os +import re +import pandas as pd +from datetime import datetime +from typing import Dict, Optional, List + +from page_objects.more_download_page import MoreDownloadPage +from globals.driver_utils import check_session_valid, reconnect_driver, go_main_click_tabber_button # 导入会话检查和重连函数 +import globals.apis as apis +import globals.global_variable as global_variable + +class UploadConfigPage: + def __init__(self, driver, wait, device_id): + self.driver = driver + self.wait = wait + self.logger = logging.getLogger(__name__) + self.more_download_page = MoreDownloadPage(driver, wait,device_id) + self.device_id = device_id + + def go_upload_config_page(self): + """点击img_2_layout(上传页面按钮)""" + try: + # 在执行操作前检查会话是否有效 + if not check_session_valid(self.driver, self.device_id): + self.logger.warning("会话已失效,尝试重新连接...") + self.driver, self.wait = reconnect_driver(self.device_id, self.driver) + self.logger.info("重新连接成功") + # 首先获取当前页面信息进行调试 + try: + current_activity = self.driver.current_activity + self.logger.info(f"当前Activity: {current_activity}") + except Exception as e: + self.logger.error(f"获取当前activity时出错: {str(e)}") + + # 尝试返回到主页面(如果不在主页面) + self.logger.info("尝试返回到主页面...") + max_back_presses = 5 # 最多按返回键次数 + back_press_count = 0 + + while back_press_count < max_back_presses: + try: + # 检查是否已经在主页面(通过检查主页面特征元素) + # 先尝试查找上传页面按钮 + try: + main_page_indicator = self.driver.find_element( + AppiumBy.ID, "com.bjjw.cjgc:id/img_2_layout" + ) + if main_page_indicator.is_displayed(): + self.logger.info("已在主页面,找到上传按钮") + break + except: + # 未找到主页面元素,继续返回 + pass + + # 按返回键 + self.driver.back() + back_press_count += 1 + self.logger.info(f"已按返回键 {back_press_count} 次") + time.sleep(1) # 等待页面响应 + except Exception as e: + self.logger.error(f"按返回键时出错: {str(e)}") + break + + # 现在尝试点击上传页面按钮 + self.logger.info("尝试点击上传页面按钮") + try: + # 使用较短的等待时间,因为我们需要快速响应 + upload_page_btn = WebDriverWait(self.driver, 5).until( + EC.element_to_be_clickable((AppiumBy.ID, "com.bjjw.cjgc:id/img_2_layout")) + ) + upload_page_btn.click() + self.logger.info("已点击img_2_layout,进入上传页面") + time.sleep(2) # 增加等待时间确保页面加载完成 + return True + except TimeoutException: + self.logger.warning("快速查找上传按钮超时,尝试使用更长的等待时间") + # 使用更长的等待时间再次尝试 + upload_page_btn = self.wait.until( + EC.element_to_be_clickable((AppiumBy.ID, "com.bjjw.cjgc:id/img_2_layout")) + ) + upload_page_btn.click() + self.logger.info("已点击img_2_layout,进入上传页面") + time.sleep(2) # 增加等待时间确保页面加载完成 + return True + except TimeoutException: + self.logger.error("等待上传页面按钮可点击超时") + return False + except Exception as e: + self.logger.error(f"点击上传页面按钮时出错: {str(e)}") + return False + + def check_change_amount_on_page(self): + """直接检查上传配置页面中是否包含变化量属性""" + try: + # 查找页面中是否包含"变化量"文本 + change_amount_elements = self.driver.find_elements( + AppiumBy.XPATH, + "//*[contains(@text, '变化量')]" + ) + + # 如果找到包含"变化量"文本的元素,返回True + if change_amount_elements: + self.logger.info("页面中包含变化量属性") + return True + else: + self.logger.info("页面中未找到变化量属性") + return False + + except Exception as e: + self.logger.error(f"检查变化量属性时出错: {str(e)}") + return False + + def click_upload_by_breakpoint_name(self, breakpoint_name): + """根据断点名称点击上传按钮""" + logging.info(f"需要点击上传按钮,断点名称:{breakpoint_name}") + try: + search_text = "" + # 提取关键部分进行模糊匹配 + if breakpoint_name.endswith('平原') and '-' in breakpoint_name: + # 从右边分割一次,取第一部分(去掉末尾的“-xxx”) + search_text = breakpoint_name.rsplit('-', 1)[0] + self.logger.info(f"处理后搜索文本:{search_text}") + else: + search_text = breakpoint_name + + # 找到包含指定断点名称的itemContainer + item_container_xpath = f"//android.widget.LinearLayout[@resource-id='com.bjjw.cjgc:id/itemContainer']//android.widget.TextView[@resource-id='com.bjjw.cjgc:id/title' and @text='{search_text}']/ancestor::android.widget.LinearLayout[@resource-id='com.bjjw.cjgc:id/itemContainer']" + + # 等待itemContainer出现 + item_container = self.wait.until( + EC.presence_of_element_located((AppiumBy.XPATH, item_container_xpath)) + ) + # self.logger.info(f"找到包含断点 {breakpoint_name} 的itemContainer") + + # 在itemContainer中查找上传按钮 + upload_btn = item_container.find_element( + AppiumBy.ID, + "com.bjjw.cjgc:id/upload_btn" + ) + + # 点击上传按钮 + upload_btn.click() + self.logger.info(f"已点击断点 {breakpoint_name} 的上传按钮") + + # 等待上传操作开始 + time.sleep(3) + + # # 检查上传是否开始 + # try: + # upload_indicator = WebDriverWait(self.driver, 20).until( + # EC.presence_of_element_located((AppiumBy.XPATH, "//*[contains(@text, '上传') or contains(@text, 'Upload')]")) + # ) + # self.logger.info(f"上传操作已开始: {upload_indicator.text}") + # except TimeoutException: + # self.logger.warning("未检测到明确的上传开始提示,但按钮点击已完成") + + return True + + except TimeoutException: + self.logger.error(f"等待断点 {breakpoint_name} 的上传按钮可点击超时") + return False + except Exception as e: + self.logger.error(f"根据断点名称点击上传按钮时出错: {str(e)}") + return False + + def handle_upload_dialog(self): + """处理上传弹窗,点击已同步按钮""" + try: + # 等待弹窗出现 + # time.sleep(2) + + # 检查弹窗是否出现 + dialog_indicators = [ + (AppiumBy.ID, "android:id/alertTitle"), + (AppiumBy.XPATH, "//android.widget.TextView[@text='提示']"), + (AppiumBy.ID, "android:id/message") + ] + + dialog_appeared = False + for by, value in dialog_indicators: + try: + element = self.driver.find_element(by, value) + if element.is_displayed(): + dialog_appeared = True + self.logger.info("检测到上传确认弹窗") + break + except: + continue + + if not dialog_appeared: + self.logger.warning("未检测到上传确认弹窗,可能不需要确认") + return True + + # 点击"已同步"按钮 + synced_btn = self.wait.until( + EC.element_to_be_clickable((AppiumBy.ID, "android:id/button2")) + ) + synced_btn.click() + self.logger.info("已点击'已同步'按钮") + + # 等待弹窗消失 + time.sleep(2) + + # 检查弹窗是否消失 + try: + # 检查弹窗是否还存在 + dialog_still_exists = False + for by, value in dialog_indicators: + try: + element = self.driver.find_element(by, value) + if element.is_displayed(): + dialog_still_exists = True + break + except: + continue + + if not dialog_still_exists: + self.logger.info("上传确认弹窗已消失") + return True + else: + self.logger.warning("上传确认弹窗仍然存在") + return False + + except Exception as e: + self.logger.info("上传确认弹窗可能已消失") + return True + + except TimeoutException: + self.logger.error("等待上传对话框可点击超时") + return False + except Exception as e: + self.logger.error(f"处理上传弹窗时出错: {str(e)}") + return False + + def execute_download_operation(self): + """执行下载操作:按返回键并点击img_5_layout""" + try: + # 按一次返回键 + self.driver.back() + self.logger.info("已按返回键") + time.sleep(1) + + # 点击img_5_layout(更多下载按钮) + more_download_btn = self.wait.until( + EC.element_to_be_clickable((AppiumBy.ID, "com.bjjw.cjgc:id/img_5_layout")) + ) + more_download_btn.click() + self.logger.info("已点击更多下载按钮") + + # 等待页面加载 + time.sleep(1) + + # 调用更多下载页面的方法对象 + try: + logging.info(f"设备开始执行更多下载页面测试") + + # 执行更多下载页面管理操作 + success = self.more_download_page.more_download_page_manager() + + if success: + logging.info(f"设备更多下载页面测试执行成功") + + + + + # 按一次返回键 + self.driver.back() + self.logger.info("已按返回键,返回到更多下载页面,准备点击上传导航按钮") + return True + else: + logging.error(f"设备更多下载页面测试执行失败") + return False + + except Exception as e: + logging.error(f"设备运行更多下载页面测试时出错: {str(e)}") + # self.take_screenshot("more_download_test_error.png") + return False + + + except TimeoutException: + self.logger.error("等待下载操作元素可点击超时") + return False + except Exception as e: + self.logger.error(f"执行下载操作时出错: {str(e)}") + return False + + def get_point_data(self): + """ + 获取三个测点的数据 + """ + point_data = [] + + try: + # 等待页面加载 + time.sleep(1) + + # 方法1: 通过resource-id获取所有测点名称 + point_name_elements = self.driver.find_elements( + AppiumBy.ID, + 'com.bjjw.cjgc:id/improve_point_name' + ) + + # 方法2: 通过text内容获取测点数据元素 + point_value_elements = self.driver.find_elements( + AppiumBy.ID, + 'com.bjjw.cjgc:id/point_values' + ) + + self.logger.info(f"找到 {len(point_name_elements)} 个测点") + self.logger.info(f"找到 {len(point_value_elements)} 个数据元素") + + # 提取每个测点的数据 + for i, (name_element, value_element) in enumerate(zip(point_name_elements, point_value_elements)): + point_info = {} + + # 获取测点名称 + point_name = name_element.text + point_info['point_name'] = point_name + + # 获取测点数据 + point_value = value_element.text + point_info['point_value'] = point_value + + # 解析详细数据 + try: + # 解析数据格式: "本期:13679.07623m; \n上期:13679.07621m; \n变化量:0.02mm; \n测量时间:2025-10-10 15:47:25.049" + data_parts = point_value.split(';') + parsed_data = {} + + for part in data_parts: + part = part.strip() + if '本期:' in part: + parsed_data['current_value'] = part.replace('本期:', '').strip() + elif '上期:' in part: + parsed_data['previous_value'] = part.replace('上期:', '').strip() + elif '变化量:' in part: + parsed_data['change_amount'] = part.replace('变化量:', '').strip() + elif '测量时间:' in part: + parsed_data['measurement_time'] = part.replace('测量时间:', '').strip() + + point_info['parsed_data'] = parsed_data + + except Exception as e: + self.logger.warning(f"解析数据时出错: {e}") + point_info['parsed_data'] = {} + + point_data.append(point_info) + + self.logger.info(f"测点 {i+1}: {point_name}") + # self.logger.info(f"完整数据: {point_value}") + + except Exception as e: + self.logger.error(f"获取数据时出错: {e}") + + return point_data + + def get_specific_point_data(self, point_name): + """ + 获取特定测点的数据 + """ + try: + # 通过文本内容查找特定测点 + point_name_element = self.driver.find_element( + AppiumBy.XPATH, + f'//android.widget.TextView[@resource-id="com.bjjw.cjgc:id/improve_point_name" and @text="{point_name}"]' + ) + + # 找到对应的数据元素 - 可能需要根据实际结构调整XPath + point_value_element = point_name_element.find_element( + AppiumBy.XPATH, + './following::android.widget.TextView[@resource-id="com.bjjw.cjgc:id/point_values"]' + ) + + return point_value_element.text + + except Exception as e: + self.logger.error(f"获取特定测点 {point_name} 数据时出错: {e}") + return None + + def swipe_up(self, start_y=1500, end_y=300, duration=500): + """ + 从指定起始Y坐标滑动到结束Y坐标(向上滑动页面) + + 参数: + start_y: 起始Y坐标 (默认1500) + end_y: 结束Y坐标 (默认300) + duration: 滑动持续时间(毫秒) (默认500) + """ + try: + # 获取屏幕尺寸 + window_size = self.driver.get_window_size() + screen_width = window_size['width'] + + # 计算X坐标(屏幕中间) + x = screen_width // 2 + + # 执行滑动操作 + self.driver.swipe(x, start_y, x, end_y, duration) + self.logger.info(f"页面已从Y:{start_y}滑动到Y:{end_y}") + + # 滑动后等待页面稳定 + time.sleep(1) + return True + + except Exception as e: + self.logger.error(f"滑动页面时出错: {e}") + return False + + def swipe_down(self, start_y=175, end_y=1310, duration=500): + """ + 从指定起始Y坐标滑动到结束Y坐标(向下滑动页面) + + 参数: + start_y: 起始Y坐标 (默认407) + end_y: 结束Y坐标 (默认1617) + duration: 滑动持续时间(毫秒) (默认500) + """ + try: + # 获取屏幕尺寸 + window_size = self.driver.get_window_size() + screen_width = window_size['width'] + + # 计算X坐标(屏幕中间) + x = screen_width // 2 + + # 执行滑动操作(向下滑动) + self.driver.swipe(x, start_y, x, end_y, duration) + self.logger.info(f"页面已从Y:{start_y}滑动到Y:{end_y}(向下滑动)") + + # 滑动后等待页面稳定 + time.sleep(1) + return True + + except Exception as e: + self.logger.error(f"向下滑动页面时出错: {e}") + return False + + def is_on_upload_config_page(self): + """通过"保存上传"按钮来确定是否在上传配置页面""" + try: + # 使用"保存上传"按钮的resource-id来检查 + save_upload_btn_locator = (AppiumBy.ID, "com.bjjw.cjgc:id/improve_save_btn") + self.wait.until(EC.presence_of_element_located(save_upload_btn_locator)) + self.logger.info("已确认在上传配置页面") + return True + except TimeoutException: + self.logger.warning("未找到保存上传按钮,不在上传配置页面") + return False + except Exception as e: + self.logger.error(f"检查上传配置页面时发生意外错误: {str(e)}") + return False + + def collect_all_point_data(self, results_dir): + """循环滑动收集所有测点数据,直到没有新数据出现""" + all_point_data = [] + seen_point_names = set() # 用于跟踪已经见过的测点名称 + max_scroll_attempts = 20 # 最大滑动次数,防止无限循环 + scroll_attempt = 0 + + self.logger.info("开始循环滑动收集所有测点数据...") + + while scroll_attempt < max_scroll_attempts: + scroll_attempt += 1 + self.logger.info(f"第 {scroll_attempt} 次尝试获取数据...") + + # 获取当前屏幕的测点数据 + current_point_data = self.get_point_data() + + if not current_point_data: + self.logger.info("当前屏幕没有测点数据,停止滑动") + break + + # 统计新发现的测点 + new_points_count = 0 + for point in current_point_data: + point_name = point.get('point_name') + if point_name and point_name not in seen_point_names: + # 新测点,添加到结果集 + all_point_data.append(point) + seen_point_names.add(point_name) + new_points_count += 1 + + self.logger.info(f"本次获取到 {len(current_point_data)} 个测点,其中 {new_points_count} 个是新测点") + + # 如果没有新数据,停止滑动 + if new_points_count == 0: + self.logger.info("没有发现新测点,停止滑动") + break + + # 滑动到下一页 + self.logger.info("滑动到下一页...") + if not self.swipe_up(): + self.logger.warning("滑动失败,停止收集") + break + + # 等待页面稳定 + time.sleep(1) + + self.logger.info(f"数据收集完成,共获取 {len(all_point_data)} 个测点数据") + return all_point_data + + def collect_check_all_point_data(self, max_variation): + """循环滑动收集所有测点数据,直到没有新数据出现""" + all_point_data = [] + seen_point_names = set() # 用于跟踪已经见过的测点名称 + max_scroll_attempts = 100 # 最大滑动次数,防止无限循环 + scroll_attempt = 0 + + self.logger.info("开始循环滑动收集所有测点数据...") + + while scroll_attempt < max_scroll_attempts: + scroll_attempt += 1 + self.logger.info(f"第 {scroll_attempt} 次尝试获取数据...") + + # 获取当前屏幕的测点数据 + current_point_data = self.get_point_data() + + if not current_point_data: + self.logger.info("当前屏幕没有测点数据,停止滑动") + break + + # 统计新发现的测点 + new_points_count = 0 + for point in current_point_data: + point_name = point.get('point_name') + if point_name and point_name not in seen_point_names: + # 新测点,添加到结果集 + all_point_data.append(point) + seen_point_names.add(point_name) + new_points_count += 1 + + self.logger.info(f"本次获取到 {len(current_point_data)} 个测点,其中 {new_points_count} 个是新测点") + + # 如果没有新数据,停止滑动 + if new_points_count == 0: + self.logger.info("没有发现新测点,停止滑动") + break + + # 滑动到下一页 + self.logger.info("滑动到下一页...") + if not self.swipe_up(): + self.logger.warning("滑动失败,停止收集") + break + + # 等待页面稳定 + time.sleep(0.2) + + self.logger.info(f"数据收集完成,共获取 {len(all_point_data)} 个测点数据") + + # 直接比对每个测点的变化量 + if max_variation is None: + self.logger.error("获取用户最大变化量失败") + max_variation = 2 + # return False + + self.logger.info(f"开始比对测点变化量,最大允许变化量: {max_variation}mm") + + for i, point in enumerate(all_point_data, 1): + point_name = point.get('point_name', '未知') + point_value = point.get('point_value', '') + + # 从完整数据中提取变化量(格式如:变化量:-0.67mm;) + change_amount_match = re.search(r'变化量:([-\d.]+)mm', point_value) + if change_amount_match: + try: + change_amount = float(change_amount_match.group(1)) + # self.logger.info(f"测点 {point_name} 变化量: {change_amount}mm, 最大允许变化量: {max_variation}mm") + + # 比较绝对值,因为变化量可能是负数 + if abs(change_amount) > max_variation: + self.logger.error(f"测点 {point_name} 变化量 {change_amount}mm 超过最大允许值 {max_variation}mm") + return False + except ValueError as e: + self.logger.error(f"解析测点 {point_name} 的变化量失败: {str(e)},原始数据: {point_value}") + return False + else: + self.logger.error(f"在测点 {point_name} 的数据中未找到变化量信息,原始数据: {point_value}") + return False + + self.logger.info(f"所有测点变化量均在允许范围内(≤{max_variation}mm)") + return True + + # def _load_user_data(self): + # """加载用户数据从Excel文件""" + # try: + # # 默认路径:当前脚本的上一级目录下的"上传人员信息.xlsx" + # current_dir = os.path.dirname(os.path.abspath(__file__)) + # parent_dir = os.path.dirname(current_dir) + # excel_path = os.path.join(parent_dir, "上传人员信息.xlsx") + + # if not os.path.exists(excel_path): + # logging.error(f"Excel文件不存在: {excel_path}") + # return False + + # # 读取Excel文件 + # df = pd.read_excel(excel_path, sheet_name='Sheet1') + + # # 处理合并单元格 - 前向填充标段列 + # df['标段'] = df['标段'].fillna(method='ffill') + + # # 清理数据:去除空行和无效数据 + # df = df.dropna(subset=['测量人员信息']) + # df = df[df['测量人员信息'].str.strip() != ''] + + # # 创建姓名到身份证的映射 + + # for _, row in df.iterrows(): + # name = row['测量人员信息'] + # id_card = str(row.iloc[4]).strip() # 第5列是身份证号 + + # # 处理身份证号格式(如果是浮点数转为整数) + # if id_card.endswith('.0'): + # id_card = id_card[:-2] + + # global_variable.GLOBAL_NAME_TO_ID_MAP[name] = id_card + + # global_variable.GLOBAL_NAME_TO_ID_MAP = df + # logging.info(f"成功加载用户数据,共 {len(df)} 条记录,{len(global_variable.GLOBAL_NAME_TO_ID_MAP)} 个唯一姓名") + # return True + + # except Exception as e: + # logging.error(f"加载用户数据失败: {str(e)}") + # return False + + def _load_user_data(self): + """加载用户数据从Excel文件,只提取名字和身份证到字典""" + try: + # 默认路径:当前脚本的上一级目录下的"上传人员信息.xlsx" + current_dir = os.path.dirname(os.path.abspath(__file__)) + parent_dir = os.path.dirname(current_dir) + excel_path = os.path.join(parent_dir, "上传人员信息.xlsx") + + if not os.path.exists(excel_path): + logging.error(f"Excel文件不存在: {excel_path}") + return False + + # 读取Excel文件 + df = pd.read_excel(excel_path, sheet_name='Sheet1') + + # 处理合并单元格 - 前向填充标段列 + # df['标段'] = df['标段'].fillna(method='ffill') + df['标段'] = df['标段'].ffill() + # 清理数据:去除空行和无效数据 + df = df.dropna(subset=['测量人员信息']) + df = df[df['测量人员信息'].str.strip() != ''] + + # 创建姓名到身份证的映射字典 + name_id_map = {} + + for _, row in df.iterrows(): + name = str(row['测量人员信息']).strip() + # 第5列是身份证号(索引为4) + id_card = str(row.iloc[4]).strip() if pd.notna(row.iloc[4]) else "" + + # 处理身份证号格式(如果是浮点数转为整数) + if id_card.endswith('.0'): + id_card = id_card[:-2] + + # 只将有效的姓名和身份证号添加到字典 + if name and id_card and len(id_card) >= 15: # 身份证号至少15位 + name_id_map[name] = id_card + else: + logging.warning(f"跳过无效数据: 姓名='{name}', 身份证='{id_card}'") + + # 将字典保存到全局变量 + global_variable.GLOBAL_NAME_TO_ID_MAP = name_id_map + + logging.info(f"成功加载用户数据,共 {len(df)} 条记录,{len(name_id_map)} 个有效姓名-身份证映射") + + # 打印前几个映射用于调试 + sample_names = list(name_id_map.keys())[:5] + for name in sample_names: + logging.debug(f"映射示例: {name} -> {name_id_map[name]}") + + return True + + except Exception as e: + logging.error(f"加载用户数据失败: {str(e)}") + return False + + def get_first_sjname_and_id(self, linecode: str, work_conditions: Dict) -> Optional[Dict]: + """ + 获取线路的第一个数据员姓名和身份证号 + + Args: + linecode: 线路编码 + + Returns: + 返回字典: {"name": 姓名, "id_card": 身份证号} + """ + if not work_conditions: + logging.error(f"无法获取线路 {linecode} 的工况信息") + return None + + # 直接取第一个测点的数据员 + first_point_info = next(iter(work_conditions.values())) + sjname = first_point_info.get('sjName') + + if not sjname: + logging.error("第一个测点没有数据员信息") + return None + + logging.info(f"使用第一个数据员: {sjname}") + + # 获取身份证号码 + id_card = global_variable.GLOBAL_NAME_TO_ID_MAP.get(sjname) + logging.info(f"id_card: {id_card}") + if not id_card: + logging.error(f"未找到数据员 {sjname} 对应的身份证号") + return None + + return {"name": sjname, "id_card": id_card} + + + # def set_all_points_and_fill_form(self, results_dir, name, user_id, condition_dict): + # """点击设置所有测点按钮并填写表单""" + # try: + # self.logger.info("开始设置所有测点并填写表单...") + + # # 点击"设置所有测点"按钮 + # set_all_points_btn = self.wait.until( + # EC.element_to_be_clickable((AppiumBy.ID, "com.bjjw.cjgc:id/display_setallpoint_btn")) + # ) + # set_all_points_btn.click() + # self.logger.info("已点击'设置所有测点'按钮") + + # # 等待表单加载 + # time.sleep(1) + + # # 填写司镜人员姓名 + # name_input = self.wait.until( + # EC.element_to_be_clickable((AppiumBy.ID, "com.bjjw.cjgc:id/sname")) + # ) + # name_input.clear() + # name_input.send_keys(name) + # self.logger.info(f"已填写司镜人员姓名: {name}") + + # # 填写司镜人员身份证号 + # id_card_input = self.wait.until( + # EC.element_to_be_clickable((AppiumBy.ID, "com.bjjw.cjgc:id/scard")) + # ) + # id_card_input.clear() + # id_card_input.send_keys(user_id) + # self.logger.info(f"已填写司镜人员身份证号: {user_id}") + + # # 选择测点状态 + # if not self._select_point_status("正常"): + # self.logger.error("选择测点状态失败") + # return False + + + # # 选择工况信息 + # if not self._select_work_condition(condition_dict): + # self.logger.error("选择工况信息失败") + # return False + + # # 等待操作完成 + # time.sleep(1) + + # # 检查操作结果 + # try: + # # 检查是否有成功提示或错误提示 + # # 这里可以根据实际应用的上传反馈机制进行调整 + # success_indicator = WebDriverWait(self.driver, 10).until( + # EC.presence_of_element_located((AppiumBy.XPATH, "//*[contains(@text, '成功') or contains(@text, '完成')]")) + # ) + # self.logger.info(f"操作完成: {success_indicator.text}") + # except TimeoutException: + # self.logger.warning("未检测到明确的操作成功提示,但操作已完成") + + # return True + + # except TimeoutException: + # self.logger.error("等待设置所有测点表单元素可点击超时") + # return False + # except Exception as e: + # self.logger.error(f"设置所有测点并填写表单时出错: {str(e)}") + # # 保存错误截图 + # error_screenshot_file = os.path.join( + # results_dir, + # f"form_error_{datetime.now().strftime('%Y%m%d_%H%M%S')}.png" + # ) + # self.driver.save_screenshot(error_screenshot_file) + # self.logger.info(f"错误截图已保存: {error_screenshot_file}") + # return False + + def _select_point_status(self, status="正常"): + logging.info(f"开始选择测点状态: {status}") + """选择测点状态""" + try: + # 点击"选择测点状态"按钮 + status_button = self.wait.until( + EC.element_to_be_clickable((AppiumBy.ID, "com.bjjw.cjgc:id/all_point_pstate_sp")) + ) + status_button.click() + + # 选择指定的状态 + status_option = self.wait.until( + EC.element_to_be_clickable((AppiumBy.XPATH, f"//android.widget.TextView[@resource-id='android:id/text1' and @text='{status}']")) + ) + status_option.click() + return True + except TimeoutException: + self.logger.error("等待测点状态选择超时") + return False + except Exception as e: + self.logger.error(f"选择测点状态时出错: {str(e)}") + return False + + + def set_all_points_and_fill_form(self, results_dir, name, user_id, main_condition_dict, minor_conditions_list): + """点击设置所有测点按钮并填写表单""" + try: + self.logger.info("开始设置所有测点并填写表单...") + + # 点击"设置所有测点"按钮 + set_all_points_btn = self.wait.until( + EC.element_to_be_clickable((AppiumBy.ID, "com.bjjw.cjgc:id/display_setallpoint_btn")) + ) + set_all_points_btn.click() + self.logger.info("已点击'设置所有测点'按钮") + + # 等待表单加载 + time.sleep(1) + + # 填写司镜人员姓名 + name_input = self.wait.until( + EC.element_to_be_clickable((AppiumBy.ID, "com.bjjw.cjgc:id/sname")) + ) + name_input.clear() + name_input.send_keys(name) + self.logger.info(f"已填写司镜人员姓名: {name}") + + # 填写司镜人员身份证号 + id_card_input = self.wait.until( + EC.element_to_be_clickable((AppiumBy.ID, "com.bjjw.cjgc:id/scard")) + ) + id_card_input.clear() + id_card_input.send_keys(user_id) + self.logger.info(f"已填写司镜人员身份证号: {user_id}") + + # 选择测点状态 + if not self._select_point_status("正常"): + self.logger.error("选择测点状态失败") + return False + + # 选择工况信息 - 现在传入两个参数 + if not self._select_work_condition(main_condition_dict, minor_conditions_list): + self.logger.error("选择工况信息失败") + return False + + # 等待操作完成 + time.sleep(1) + + # 检查操作结果 + try: + success_indicator = WebDriverWait(self.driver, 10).until( + EC.presence_of_element_located((AppiumBy.XPATH, "//*[contains(@text, '成功') or contains(@text, '完成')]")) + ) + self.logger.info(f"操作完成: {success_indicator.text}") + except TimeoutException: + self.logger.warning("未检测到明确的操作成功提示,但操作已完成") + + return True + except TimeoutException: + self.logger.error("等待设置所有测点表单元素可点击超时") + return False + except Exception as e: + self.logger.error(f"设置所有测点并填写表单时出错: {str(e)}") + return False + + def _select_work_condition(self, main_condition_dict: Dict[str, List[str]], minor_conditions_list: List[Dict]): + """根据主要工况字典和次要工况列表选择工况信息""" + self.logger.info("开始选择工况信息") + self.logger.info(f"主要工况: {main_condition_dict}") + self.logger.info(f"次要工况数量: {len(minor_conditions_list)}") + + # 工点类型编码与界面控件ID的映射 + work_type_mapping = { + "1": { # 隧道 + "button_id": "com.bjjw.cjgc:id/all_point_workinfo_sp_suidao", + "name": "隧道" + }, + "2": { # 区间路基 + "button_id": "com.bjjw.cjgc:id/all_point_workinfo_sp_luji", + "name": "路基" + }, + "3": { # 桥 + "button_id": "com.bjjw.cjgc:id/all_point_workinfo_sp_qiaoliang", + "name": "梁桥" + }, + "4": { # 涵洞 + "button_id": "com.bjjw.cjgc:id/all_point_workinfo_sp_handong", + "name": "涵洞" + } + } + + try: + success_count = 0 + + # 第一步:为每个工点类型选择主要工况 + for work_type, workinfo_names in main_condition_dict.items(): + work_type_str = str(work_type).strip() + if work_type_str not in work_type_mapping: + self.logger.warning(f"未知的工点类型编码: {work_type_str},跳过") + continue + + mapping = work_type_mapping[work_type_str] + button_id = mapping["button_id"] + work_type_name = mapping["name"] + + if workinfo_names: + workinfo_name = workinfo_names[0] # 主要工况 + self.logger.info(f"为{work_type_name}({work_type_str})选择主要工况: {workinfo_name}") + + try: + # 点击工况选择按钮 + condition_button = self.wait.until( + EC.element_to_be_clickable((AppiumBy.ID, button_id)) + ) + condition_button.click() + self.logger.info(f"成功点击{work_type_name}工况选择按钮") + + # 选择主要的工况选项 + if self._select_condition_option(workinfo_name): + self.logger.info(f"成功为{work_type_name}选择主要工况: {workinfo_name}") + success_count += 1 + else: + self.logger.warning(f"未能为{work_type_name}选择主要工况: {workinfo_name}") + + except TimeoutException: + self.logger.error(f"等待{work_type_name}工况选择按钮超时") + except Exception as e: + self.logger.error(f"点击{work_type_name}工况按钮时出错: {str(e)}") + + # 第二步:如果有次要工况,滑动页面处理 + if minor_conditions_list: + self.logger.info(f"开始处理 {len(minor_conditions_list)} 个次要工况") + minor_success_count = self._handle_minor_work_conditions(minor_conditions_list) + self.logger.info(f"次要工况处理完成: 成功 {minor_success_count}/{len(minor_conditions_list)} 个") + else: + self.logger.info("没有次要工况需要处理") + + self.logger.info(f"工况选择完成: 主要工况成功{success_count}/{len(main_condition_dict)}个") + return success_count > 0 # 只要有一个主要工况成功就返回True + + except Exception as e: + self.logger.error(f"选择工况信息时发生未知错误: {str(e)}") + return False + + def _handle_minor_work_conditions(self, minor_conditions_list: List[Dict]) -> int: + """处理次要工况:滑动页面,找到对应测点并设置工况""" + success_count = 0 + processed_points = set() # 记录已处理的测点,避免重复处理 + logging.info(f"要处理的次要工况:{minor_conditions_list}") + + # 提取所有需要处理的测点ID + target_point_ids = {condition['point_id'] for condition in minor_conditions_list} + self.logger.info(f"目标测点ID: {list(target_point_ids)}") + + # 点击"关闭设置所有"按钮 + close_set_all_points_btn = self.wait.until( + EC.element_to_be_clickable((AppiumBy.ID, "com.bjjw.cjgc:id/display_setallpoint_btn")) + ) + close_set_all_points_btn.click() + self.logger.info("已点击'关闭设置所有测点'按钮") + + try: + max_scroll_attempts = 20 # 最大滑动次数防止无限循环 + scroll_attempt = 0 + previous_points = set() # 记录上一页的测点 + remaining_conditions = minor_conditions_list.copy() + + while scroll_attempt < max_scroll_attempts: + scroll_attempt += 1 + self.logger.info(f"第 {scroll_attempt} 次滑动处理次要工况...") + + # 获取当前页面的测点 + current_points = set(self.collect_points_on_page()) + self.logger.info(f"当前页面共找到 {len(current_points)} 个测点: {list(current_points)}") + + # 检查是否有新测点 + if current_points == previous_points: + self.logger.info("当前页面没有新的测点,停止滑动") + break + + # 检查当前页面是否有目标测点 + current_target_points = current_points & target_point_ids + if not current_target_points: + self.logger.info(f"当前页面没有目标测点,继续滑动查找...") + # 滑动到下一页 + if not self.swipe_down(): + self.logger.warning("滑动失败,停止处理") + break + # 等待页面稳定 + time.sleep(1) + previous_points = current_points + continue + + self.logger.info(f"当前页面找到目标测点: {list(current_target_points)}") + + # 如果当前测点列表的测点名,在minor_conditions_list中,就点击选择测点对应工况 + + # 处理当前页面的次要工况 + page_success_count = self._process_minor_conditions_on_current_page( + current_points, remaining_conditions, processed_points + ) + success_count += page_success_count + + # 更新剩余待处理的工况列表 + remaining_conditions = [ + condition for condition in remaining_conditions + if condition['point_id'] not in processed_points + ] + + self.logger.info(f"剩余待处理工况: {len(remaining_conditions)} 个") + if remaining_conditions: + remaining_points = [cond['point_id'] for cond in remaining_conditions] + self.logger.info(f"剩余测点: {remaining_points}") + + # 更新已处理的测点记录 + previous_points = current_points + + # 如果所有次要工况都已处理完成,提前退出 + if len(processed_points) >= len(minor_conditions_list): + self.logger.info(f"所有 {len(minor_conditions_list)} 个次要工况已处理完成") + break + + # 滑动到下一页 + self.logger.info("滑动到下一页继续查找...") + if not self.swipe_down(): + self.logger.warning("滑动失败,停止处理") + break + + # 等待页面稳定 + time.sleep(1) + + self.logger.info(f"次要工况处理完成: 成功 {success_count}/{len(minor_conditions_list)} 个") + return success_count + + except Exception as e: + self.logger.error(f"处理次要工况时出错: {str(e)}") + return success_count + + def _process_minor_conditions_on_current_page(self, current_points: set, minor_conditions_list: List[Dict], processed_points: set) -> int: + """处理当前页面上的次要工况""" + page_success_count = 0 + + try: + self.logger.info("开始处理当前页面上的次要工况...") + + # 1. 先定位所有测点的根容器 + point_containers = self.driver.find_elements( + AppiumBy.ID, + "com.bjjw.cjgc:id/layout_popup_top" + ) + self.logger.info(f"当前页面找到 {len(point_containers)} 个测点容器") + + if not point_containers: + self.logger.warning("当前页面未找到任何测点容器") + return 0 + + # 2. 构建当前页面测点的映射关系:测点名称 -> 容器元素 + point_container_map = {} + for container in point_containers: + try: + name_element = container.find_element( + AppiumBy.ID, + "com.bjjw.cjgc:id/improve_point_name" + ) + point_name = name_element.text.strip() + if point_name and point_name in current_points: + point_container_map[point_name] = container + self.logger.debug(f"映射测点: {point_name}") + except NoSuchElementException: + continue + except Exception as e: + self.logger.debug(f"解析测点容器时出错: {str(e)}") + continue + + # 3. 遍历需要处理的次要工况列表 + for minor_condition in minor_conditions_list: + point_id = minor_condition['point_id'] + workinfoname = minor_condition['workinfoname'] + work_type = minor_condition['work_type'] + + # 检查是否已经处理过 + if point_id in processed_points: + self.logger.debug(f"测点 {point_id} 已处理过,跳过") + continue + + # 检查是否在当前页面 + if point_id not in current_points: + self.logger.debug(f"测点 {point_id} 不在当前页面") + continue + + # 检查是否有对应的容器 + if point_id not in point_container_map: + self.logger.warning(f"测点 {point_id} 在当前页面但未找到对应容器") + continue + + try: + self.logger.info(f"开始处理测点 {point_id} 的次要工况: {workinfoname}") + + # 4. 在当前测点的容器内查找工况选择按钮 + container = point_container_map[point_id] + workinfo_button = container.find_element( + AppiumBy.ID, + "com.bjjw.cjgc:id/point_workinfo_sp" + ) + + # 验证按钮是否可见和可用 + if workinfo_button.is_displayed() and workinfo_button.is_enabled(): + workinfo_button.click() + self.logger.info(f"已点击测点 {point_id} 的工况选择按钮") + + # 选择对应的工况选项 + if self._select_minor_conditions_option(workinfoname, work_type): + page_success_count += 1 + processed_points.add(point_id) + self.logger.info(f"成功为测点 {point_id} 设置次要工况: {workinfoname}") + else: + self.logger.warning(f"为测点 {point_id} 选择工况选项失败") + else: + self.logger.warning(f"测点 {point_id} 的工况选择按钮不可用") + + except NoSuchElementException: + self.logger.warning(f"未找到测点 {point_id} 的工况选择按钮") + except Exception as e: + self.logger.error(f"处理测点 {point_id} 时出错: {str(e)}") + + self.logger.info(f"当前页面成功处理 {page_success_count} 个测点的次要工况") + return page_success_count + + + except Exception as e: + self.logger.error(f"处理当前页面次要工况时发生异常: {str(e)}") + return page_success_count + + def _select_minor_conditions_option(self, option_name: str, work_type_name: str) -> bool: + """根据工况名称选择对应的下拉列表中的选项""" + try: + self.logger.info(f"开始选择次要工况选项: {option_name}") + + # 方法1: 通过文本精确匹配 + try: + option_xpath = f"//android.widget.TextView[@text='{option_name}']" + option_element = WebDriverWait(self.driver, 5).until( + EC.element_to_be_clickable((AppiumBy.XPATH, option_xpath)) + ) + option_element.click() + self.logger.info(f"通过文本匹配成功选择工况: {option_name}") + return True + except TimeoutException: + self.logger.debug(f"通过文本 '{option_name}' 未找到工况选项") + + # 方法2: 通过列表项ID查找 + try: + list_items = self.driver.find_elements(AppiumBy.ID, "android:id/text1") + for item in list_items: + if item.text == option_name: + item.click() + self.logger.info(f"通过列表项成功选择工况: {option_name}") + return True + except Exception as e: + self.logger.debug(f"通过列表项查找失败: {str(e)}") + + # 方法3: 滑动查找 + max_scroll_attempts = 3 + for attempt in range(max_scroll_attempts): + try: + option_element = self.driver.find_element( + AppiumBy.XPATH, + f"//android.widget.TextView[@text='{option_name}']" + ) + option_element.click() + self.logger.info(f"通过滑动后查找成功选择工况: {option_name}") + return True + except NoSuchElementException: + self.logger.debug(f"第 {attempt + 1} 次滑动查找未找到选项: {option_name}") + # 执行滑动 + self._scroll_condition_options() + except Exception as e: + self.logger.debug(f"滑动查找时出错: {str(e)}") + break + + self.logger.error(f"所有方法都无法找到工况选项: {option_name}") + return False + + except TimeoutException: + self.logger.error(f"等待工况选项可点击超时: {option_name}") + return False + except Exception as e: + self.logger.error(f"选择工况选项时出错: {str(e)}") + return False + + def _scroll_condition_options(self): + """滑动工况选项列表""" + try: + # 获取屏幕尺寸 + window_size = self.driver.get_window_size() + start_x = window_size['width'] * 0.5 + start_y = window_size['height'] * 0.7 + end_y = window_size['height'] * 0.3 + + # 执行滑动 + self.driver.swipe(start_x, start_y, start_x, end_y, 500) + self.logger.debug("执行滑动操作查找更多工况选项") + time.sleep(1) # 等待滑动完成 + + except Exception as e: + self.logger.error(f"滑动工况选项列表时出错: {str(e)}") + + def collect_points_on_page(self) -> List[str]: + """ + 收集当前页面中存在的测点ID列表 + 要求:必须同时存在"com.bjjw.cjgc:id/improve_point_name"和"com.bjjw.cjgc:id/point_workinfo_sp"才返回 + """ + point_ids = [] + try: + self.logger.info("开始收集当前页面中的测点ID...") + + # 1. 先定位所有测点的根容器(RelativeLayout),确保在同一容器内查找子元素 + point_containers = self.driver.find_elements( + AppiumBy.ID, + "com.bjjw.cjgc:id/layout_popup_top" # 测点根容器的resource-id + ) + self.logger.info(f"找到 {len(point_containers)} 个测点根容器") + + for container in point_containers: + try: + # 2. 在当前根容器内查找测点名称元素 + name_element = container.find_element( + AppiumBy.ID, + "com.bjjw.cjgc:id/improve_point_name" + ) + point_name = name_element.text + if not point_name: + self.logger.debug("测点名称为空,跳过") + continue + + # 3. 在当前根容器内查找工况按钮(按实际层级定位) + # 层级:RelativeLayout -> LinearLayout -> LinearLayout -> Button + workinfo_button = container.find_element( + AppiumBy.XPATH, + ".//android.widget.LinearLayout/android.widget.LinearLayout/android.widget.Button[@resource-id='com.bjjw.cjgc:id/point_workinfo_sp']" + ) + + # 4. 验证按钮是否可见 + if workinfo_button.is_displayed(): + point_ids.append(point_name) + self.logger.debug(f"找到有效测点: {point_name}") + + except NoSuchElementException as e: + # 若名称或工况按钮不存在,跳过当前容器 + self.logger.debug(f"测点容器中缺少必要元素: {str(e)}") + continue + except Exception as e: + self.logger.debug(f"解析测点容器时出错: {str(e)}") + continue + + self.logger.info(f"当前页面共找到 {len(point_ids)} 个有效测点: {point_ids}") + return point_ids + + except Exception as e: + self.logger.error(f"收集页面测点ID时出错: {str(e)}") + return [] + + def collect_all_points_on_page(self) -> List[Dict]: + """滑动收集页面中所有测点的信息""" + all_points = [] + seen_point_names = set() + max_scroll_attempts = 50 + scroll_attempt = 0 + + self.logger.info("开始滑动收集页面中所有测点...") + + while scroll_attempt < max_scroll_attempts: + scroll_attempt += 1 + self.logger.info(f"第 {scroll_attempt} 次滑动收集...") + + # 获取当前屏幕的测点 + current_points = self._get_current_screen_points_detail() + + if not current_points: + self.logger.info("当前屏幕没有测点数据") + break + + # 添加新发现的测点 + new_points_count = 0 + for point in current_points: + point_name = point.get('point_name') + if point_name and point_name not in seen_point_names: + all_points.append(point) + seen_point_names.add(point_name) + new_points_count += 1 + + self.logger.info(f"本次获取到 {len(current_points)} 个测点,其中 {new_points_count} 个是新测点") + + if new_points_count == 0: + self.logger.info("没有发现新测点,停止滑动") + break + + # 滑动到下一页 + if not self.swipe_up(): + self.logger.warning("滑动失败,停止收集") + break + + time.sleep(0.5) + + self.logger.info(f"共收集到 {len(all_points)} 个测点的详细信息") + return all_points + + def _get_current_screen_points_detail(self) -> List[Dict]: + """获取当前屏幕测点的详细信息""" + points = [] + + try: + # 查找所有测点容器 + point_containers = self.driver.find_elements( + AppiumBy.XPATH, + "//android.widget.LinearLayout[@resource-id='com.bjjw.cjgc:id/layout_popup_top']" + ) + + for container in point_containers: + try: + point_info = {} + + # 获取测点名称(作为point_id的替代) + name_element = container.find_element( + AppiumBy.ID, "com.bjjw.cjgc:id/improve_point_name" + ) + point_name = name_element.text + point_info['point_name'] = point_name + point_info['point_id'] = point_name # 使用名称作为ID,或者根据实际情况调整 + + # 获取测点元素引用,用于后续操作 + point_info['element'] = container + + # 获取当前工况信息 + try: + workinfo_element = container.find_element( + AppiumBy.ID, "com.bjjw.cjgc:id/point_workinfo_sp" + ) + point_info['current_workinfo'] = workinfo_element.text + point_info['workinfo_element'] = workinfo_element + except NoSuchElementException: + point_info['current_workinfo'] = "" + point_info['workinfo_element'] = None + + points.append(point_info) + + except Exception as e: + self.logger.debug(f"解析单个测点信息时出错: {str(e)}") + continue + + except Exception as e: + self.logger.error(f"获取当前屏幕测点详细信息时出错: {str(e)}") + + return points + + def _set_single_point_work_condition(self, point_data: Dict, workinfo_name: str, work_type: str) -> bool: + """为单个测点设置工况信息""" + try: + point_name = point_data.get('point_name') + self.logger.info(f"开始为测点 {point_name} 设置工况: {workinfo_name}") + + # 使用保存的元素引用点击工况选择按钮 + workinfo_element = point_data.get('workinfo_element') + if workinfo_element: + workinfo_element.click() + time.sleep(1) # 等待选项弹出 + + # 选择指定的工况 + if self._select_condition_option(workinfo_name): + self.logger.info(f"成功为测点 {point_name} 设置工况: {workinfo_name}") + return True + else: + self.logger.warning(f"为测点 {point_name} 选择工况选项失败") + return False + else: + self.logger.warning(f"未找到测点 {point_name} 的工况选择按钮") + return False + + except Exception as e: + self.logger.error(f"为测点 {point_name} 设置工况时出错: {str(e)}") + return False + + def _select_condition_option(self, condition_name: str) -> bool: + """选择具体的工况选项 + + Args: + condition_name: 工况名称 + + Returns: + bool: 是否选择成功 + """ + try: + self.logger.info(f"开始选择工况选项: {condition_name}") + + # 方法1: 通过文本查找并点击 + try: + option_xpath = f"//android.widget.TextView[@text='{condition_name}']" + option_element = WebDriverWait(self.driver, 5).until( + EC.element_to_be_clickable((AppiumBy.XPATH, option_xpath)) + ) + option_element.click() + self.logger.info(f"通过文本定位成功选择工况: {condition_name}") + return True + except TimeoutException: + self.logger.debug(f"通过文本'{condition_name}'未找到工况选项") + + # 方法2: 通过列表项查找 + try: + # 假设工况选项在列表中,点击第一个可用的选项 + list_item = WebDriverWait(self.driver, 5).until( + EC.element_to_be_clickable((AppiumBy.ID, "android:id/text1")) + ) + list_item.click() + self.logger.info("通过列表项选择工况") + return True + except TimeoutException: + self.logger.debug("未找到列表项形式的工况选项") + + # 方法3: 尝试点击屏幕特定位置(备选方案) + try: + # 获取屏幕尺寸 + window_size = self.driver.get_window_size() + x = window_size['width'] // 2 + y = window_size['height'] // 2 + + # 点击屏幕中央(假设选项在中间) + self.driver.tap([(x, y)]) + self.logger.info("通过点击屏幕中央选择工况") + return True + except Exception as e: + self.logger.debug(f"点击屏幕中央失败: {str(e)}") + + self.logger.error(f"所有方法都无法选择工况选项: {condition_name}") + return False + + except Exception as e: + self.logger.error(f"选择工况选项时出错: {str(e)}") + return False + + def _scroll_to_find_condition(self): + """滑动查找工况选项的辅助方法""" + try: + # 获取屏幕尺寸 + window_size = self.driver.get_window_size() + start_x = window_size['width'] * 0.5 + start_y = window_size['height'] * 0.7 + end_y = window_size['height'] * 0.3 + + # 执行滑动 + self.driver.swipe(start_x, start_y, start_x, end_y, 500) + logging.debug("执行滑动操作") + + except Exception as e: + logging.error(f"滑动操作时出错: {str(e)}") + + + def aging_down_data(self, breakpoint_name=None, retry_count=0): + """跳转上传配置页面,根据断点名称点击上传按钮,检查是否在上传配置页面 + ->变化量属性,执行下载操作""" + try: + self.logger.info("开始执行上传配置页面操作aging") + + # # 跳转到上传配置页面 + # if not self.go_upload_config_page(): + # self.logger.error("跳转上传配置页面失败") + # return False + # 跳转到上传配置页面 + if not go_main_click_tabber_button(self.driver, self.device_id, "com.bjjw.cjgc:id/img_2_layout"): + logging.error(f"设备 {self.device_id} 跳转到上传配置页面失败") + return False + + # 根据断点名称点击上传按钮 + if not self.click_upload_by_breakpoint_name(breakpoint_name): + self.logger.error("点击上传按钮失败") + return False + + if not self.handle_upload_dialog(): + self.logger.error("处理上传对话框失败") + return False + + self.logger.info("开始执行上传配置页面测试") + + # 检查是否在上传配置页面 + if not self.is_on_upload_config_page(): + self.logger.info("不在上传配置页面,尝试导航...") + return False + + # 检查是否有变化量属性 + if not self.check_change_amount_on_page(): + self.logger.info("页面中缺少变化量属性,执行下载操作") + + # 执行下载操作 + if not self.execute_download_operation(): + self.logger.error("下载操作执行失败") + return False + time.sleep(1) + self.logger.info("已返回上传配置页面") + + # 如果是第一次重试,再次调用aging_down_data + if retry_count < 1: + self.logger.info(f"第{retry_count+1}次重试,再次执行aging_down_data流程") + return self.aging_down_data(breakpoint_name, retry_count + 1) + else: + # 第二次仍然缺少变化量属性,返回错误 + self.logger.error("已执行两次下载操作,页面仍然缺少变化量属性") + return False + else: + self.logger.info("页面中包含变化量属性,继续执行后续操作") + return True + except Exception as e: + self.logger.error(f"设备执行上传重复下载数据操作失败:{e}") + return False + + def click_save_upload_and_handle_dialogs(self): + """点击保存上传并处理弹窗""" + try: + self.logger.info("开始点击保存上传并处理弹窗") + + # 点击保存上传按钮 + save_upload_btn = WebDriverWait(self.driver, 10).until( + EC.element_to_be_clickable((AppiumBy.ID, "com.bjjw.cjgc:id/improve_save_btn")) + ) + save_upload_btn.click() + self.logger.info("已点击保存上传按钮") + + # 处理警告弹窗 + time.sleep(1) + if not self.handle_warning_dialog(): + self.logger.error("处理警告弹窗失败") + return False + + return True + + except TimeoutException: + self.logger.error("点击保存上传按钮超时") + return False + except Exception as e: + self.logger.error(f"点击保存上传并处理弹窗时出错: {str(e)}") + return False + + def handle_warning_dialog(self): + """处理警告弹窗""" + try: + self.logger.info("检查并处理警告弹窗") + + # 等待弹窗出现 + warning_dialog = WebDriverWait(self.driver, 10).until( + EC.presence_of_element_located((AppiumBy.ID, "android:id/parentPanel")) + ) + + # 获取弹窗文本确认是目标弹窗 + alert_title = warning_dialog.find_element(AppiumBy.ID, "android:id/alertTitle") + alert_message = warning_dialog.find_element(AppiumBy.ID, "android:id/message") + + self.logger.info(f"检测到弹窗 - 标题: {alert_title.text}, 消息: {alert_message.text}") + + # 根据业务逻辑选择"是"或"否" + # 这里选择"是"来上传本次数据 + yes_button = warning_dialog.find_element(AppiumBy.ID, "android:id/button1") + yes_button.click() + self.logger.info("已点击'是'按钮确认上传") + # no_button = warning_dialog.find_element(AppiumBy.ID, "android:id/button3") + # no_button.click() + # self.driver.back() + # self.logger.info("已点击'否'按钮取消上传和返回按钮") + + return True + + except TimeoutException: + self.logger.info("未检测到警告弹窗,继续流程") + return True # 没有弹窗也是正常情况 + except Exception as e: + self.logger.error(f"处理警告弹窗时出错: {str(e)}") + return False + + def wait_for_upload_completion(self): + """等待上传完成""" + try: + # time.sleep(2) + self.logger.info("开始等待上传完成") + + # 等待弹窗显示 + upload_list = WebDriverWait(self.driver, 10).until( + EC.presence_of_element_located((AppiumBy.ID, "android:id/customPanel")) + ) + #等待弹窗消失 + WebDriverWait(self.driver, 20).until( + EC.invisibility_of_element_located((AppiumBy.ID, "android:id/customPanel")) + ) + + self.logger.info("已返回到上传列表页面,上传已完成") + return True + + except TimeoutException: + self.logger.error("等待上传完成超时") + return False + except Exception as e: + self.logger.error(f"等待上传完成时出错: {str(e)}") + return False + + + def parse_work_conditions(self, work_conditions): + """ + 解析工况信息,区分主要工况和次要工况 + 返回: (主要工况字典, 次要工况列表) + """ + if not work_conditions: + logging.warning("工况数据为空") + return {}, [] + + try: + # 统计每个work_type下各个workinfoname的出现次数 + work_type_stats = {} + point_work_conditions = {} # 记录每个测点的工况信息 + + for point_id, condition_data in work_conditions.items(): + work_type = str(condition_data.get('work_type', '')) + workinfoname = condition_data.get('workinfoname', '') + sjname = condition_data.get('sjName', '') + + # 记录测点工况信息 + point_work_conditions[point_id] = { + 'work_type': work_type, + 'workinfoname': workinfoname, + 'sjname': sjname + } + + # 统计出现次数 + if work_type and workinfoname: + if work_type not in work_type_stats: + work_type_stats[work_type] = {} + + if workinfoname in work_type_stats[work_type]: + work_type_stats[work_type][workinfoname] += 1 + else: + work_type_stats[work_type][workinfoname] = 1 + + # 分离主要工况和次要工况 + main_condition_dict = {} # 主要工况字典 {work_type: [主要workinfoname]} + minor_conditions_list = [] # 次要工况列表 [{point_id, work_type, workinfoname}] + + # 定义阈值:出现次数少于这个值的认为是次要工况 + minor_threshold = 3 # 可以根据实际情况调整 + + for work_type, workinfoname_counts in work_type_stats.items(): + if workinfoname_counts: + # 按出现次数从多到少排序 + sorted_workinfonames = sorted(workinfoname_counts.items(), key=lambda x: x[1], reverse=True) + + # 主要工况:取出现次数最多的 + if sorted_workinfonames: + main_workinfoname = sorted_workinfonames[0][0] + main_condition_dict[work_type] = [main_workinfoname] + + logging.info(f"work_type {work_type} 的主要工况: '{main_workinfoname}' (出现{sorted_workinfonames[0][1]}次)") + + # 次要工况:收集所有出现次数较少的工况及其对应测点 + for workinfoname, count in sorted_workinfonames: + if count <= minor_threshold: + # 找到使用这个次要工况的所有测点 + for point_id, point_info in point_work_conditions.items(): + if (point_info['work_type'] == work_type and + point_info['workinfoname'] == workinfoname): + minor_conditions_list.append({ + 'point_id': point_id, + 'work_type': work_type, + 'workinfoname': workinfoname, + 'sjname': point_info['sjname'], + 'count': count + }) + + logging.info(f"解析结果: 主要工况 {len(main_condition_dict)} 种,次要工况 {len(minor_conditions_list)} 个测点") + + # 打印次要工况详情用于调试 + for minor_condition in minor_conditions_list: + logging.info(f"次要工况 - 测点 {minor_condition['point_id']}: work_type={minor_condition['work_type']}, workinfoname='{minor_condition['workinfoname']}'") + + return main_condition_dict, minor_conditions_list + + except Exception as e: + logging.error(f"解析工况信息时发生错误: {str(e)}") + return {}, [] + + def get_work_type_name(work_type: str) -> str: + """ + 根据工点类型编码获取类型名称 + + Args: + work_type: 工点类型编码(1-隧道,2-区间路基,3-桥, 4-涵洞) + + Returns: + 工点类型名称 + """ + work_type_mapping = { + "1": "隧道", + "2": "区间路基", + "3": "桥", + "4": "涵洞" + } + return work_type_mapping.get(work_type, f"未知类型({work_type})") + + def upload_config_page_manager(self, results_dir, breakpoint_name=None, line_num=None): + """执行上传配置页面管理操作""" + try: + # 保存参数为实例属性 + self.results_dir = results_dir + self.breakpoint_name = breakpoint_name + self.line_num = line_num + self.logger.info("开始执行上传配置页面操作manager") + + # 跳转到上传配置页面 + if not go_main_click_tabber_button(self.driver, self.device_id, "com.bjjw.cjgc:id/img_2_layout"): + logging.error(f"设备 {self.device_id} 跳转到测量页面失败") + return False + + # 根据断点名称点击上传按钮 + if not self.click_upload_by_breakpoint_name(breakpoint_name): + self.logger.error("点击上传按钮失败") + return False + + if not self.handle_upload_dialog(): + self.logger.error("处理上传对话框失败") + return False + + self.logger.info("开始执行上传配置页面测试") + + # 检查是否在上传配置页面 + if not self.is_on_upload_config_page(): + self.logger.info("不在上传配置页面,尝试导航...") + return False + + + # 检查是否有变化量属性,有就执行下面代码,没有就执行重新下载函数 + # 直接检查页面中是否有"变化量"属性 + if not self.check_change_amount_on_page(): + self.logger.info("页面中缺少变化量属性,执行下载操作") + # 执行下载操作 + if not self.execute_download_operation(): + self.logger.error("下载操作执行失败") + return False + # 下载操作完成后,点击上传导航按钮跳转到上传页面执行更多下载操作并检查状态 + if not self.aging_down_data(breakpoint_name): + self.logger.error("三次下载都失败,属性不存在变量") + return False + else: + self.logger.info("页面中包含变化量属性,继续执行后续操作") + + user_id = global_variable.GLOBAL_USERNAME + if user_id is None: + self.logger.error("获取用户ID失败") + return False + + max_variation = apis.get_user_max_variation(user_id) + if max_variation is None: + self.logger.error("获取用户最大变化量失败") + return False + + # # 循环滑动收集所有测点数据 + # logging.info("准备循环滑动收集所有测点数据") + # all_point_data = self.collect_all_point_data(results_dir) + + # # 保存测试结果 + # result_file = os.path.join(results_dir, f"{self.line_num}_{datetime.now().strftime('%Y%m%d')}.txt") + # with open(result_file, 'w', encoding='utf-8') as f: + # f.write(f"测试时间: {datetime.now().strftime('%Y-%m-%d')}\n") + # f.write(f"获取到的测点数: {len(all_point_data)}\n\n") + # for i, point in enumerate(all_point_data, 1): + # f.write(f"测点 {i}: {point.get('point_name', '未知')}\n") + # parsed = point.get('parsed_data', {}) + # # if parsed: + # # f.write(f" 本期数值: {parsed.get('current_value', 'N/A')}\n") + # # f.write(f" 上期数值: {parsed.get('previous_value', 'N/A')}\n") + # # f.write(f" 变化量: {parsed.get('change_amount', 'N/A')}\n") + # # f.write(f" 测量时间: {parsed.get('measurement_time', 'N/A')}\n") + # f.write(f"完整数据:\n{point.get('point_value', 'N/A')}\n\n") + + # self.logger.info(f"测试结果已保存到: {result_file}") + + # 给ai接口发送文件,等待接口返回是否能上传数据 + # 不能就点击手机返回按钮,能就继续填写表单 + # if not self.check_ai_upload_permission(result_file): + # self.logger.info("AI接口返回不允许上传,点击返回按钮") + # self.driver.back() + # return True # 返回True继续下一个断点的上传配置。 + + if not self.collect_check_all_point_data(max_variation): + self.logger.error(f"断点 '{breakpoint_name}' 上传失败") + self.driver.back() + return False # 返回False继续下一个断点的上传配置。 + + + # 获取线路的所有工况信息 + work_conditions = apis.get_work_conditions_by_linecode(self.line_num) + # work_conditions = {'1962527': {'sjName': '王顺', 'workinfoname': '轨道板(道床)铺设后,第1个月', 'work_type': 2}, + # '0299815Z2': {'sjName': '王顺', 'workinfoname': '冬休', 'work_type': 2}, + # '0299820H1': {'sjName': '王顺', 'workinfoname': '架桥机(运梁车) 首次通过后', 'work_type': 4}, + # '0431248D1': {'sjName': '王顺', 'workinfoname': '轨道板(道床)铺设后,第1个月', 'work_type': 4}, + # '0431248D2': {'sjName': '王顺', 'workinfoname': '轨道板(道床)铺设后,第1个月', 'work_type': 4}, + # '0299815Z1': {'sjName': '王顺', 'workinfoname': '架桥机(运梁车) 首次通过前', 'work_type': 2}, + # '0431289D2': {'sjName': '王顺', 'workinfoname': '轨道板(道床)铺设后,第1个月', 'work_type': 4}, + # '0431330D1': {'sjName': '王顺', 'workinfoname': '轨道板(道床)铺设后,第1个月', 'work_type': 2}, + # '0431330D2': {'sjName': '王顺', 'workinfoname': '轨道板(道床)铺设后,第1个月', 'work_type': 2}, + # '0431370D1': {'sjName': '王顺', 'workinfoname': '轨道板(道床)铺设后,第1个月', 'work_type': 2}} + self.logger.info(f"获取线路工况信息成功: {work_conditions}") + if not work_conditions: + self.logger.error("获取工况信息失败") + return False + + # 提取人员姓名和身份证 + if not self._load_user_data(): + self.logger.error("加载用户数据失败") + return False + + # 获取第一个数据员姓名和身份证号 + user_info = self.get_first_sjname_and_id(self.line_num, work_conditions) + self.logger.info(f"获取到的第一个数据员姓名和身份证号为:{user_info}") + + if not user_info: + self.logger.error(f"无法获取线路 '{self.line_num}' 的数据员信息") + return False + + + # 解析工况信息,现在返回两个值:主要工况字典和次要工况列表 + main_condition_dict, minor_conditions_list = self.parse_work_conditions(work_conditions) + self.logger.info(f"主要工况: {main_condition_dict}") + self.logger.info(f"次要工况数量: {len(minor_conditions_list)}") + # 设置所有测点并填写表单 - 传入两个参数 + if not self.set_all_points_and_fill_form(results_dir, user_info.get("name"), user_info.get("id_card"), main_condition_dict, minor_conditions_list): + self.logger.error("设置所有测点并填写表单失败") + return False + + + # # 表达填写完成,点击"保存上传"并处理弹窗 + # if not self.click_save_upload_and_handle_dialogs(): + # self.logger.error("点击保存上传并处理弹窗失败") + # return False + + # 暂不上传,使用返回按钮替代。 + self.driver.back() + + # 等待上传,查看loading弹窗。没有就下一个 + if not self.wait_for_upload_completion(): + self.logger.error("等待上传完成失败") + return False + + self.logger.info("上传配置页面操作执行完成") + # 把上传成功的断点写入全局变量GLOBAL_UPLOAD_SUCCESS_BREAKPOINT_LIST + global_variable.GLOBAL_UPLOAD_SUCCESS_BREAKPOINT_LIST.append(breakpoint_name) + + return True + + except Exception as e: + self.logger.error(f"执行上传配置页面操作时出错: {str(e)}") + # # 保存错误截图 + # error_screenshot_file = os.path.join( + # results_dir, + # f"upload_config_error_{datetime.now().strftime('%Y%m%d_%H%M%S')}.png" + # ) + # self.driver.save_screenshot(error_screenshot_file) + # self.logger.info(f"错误截图已保存: {error_screenshot_file}") + return False \ No newline at end of file diff --git a/permissions.py b/permissions.py new file mode 100644 index 0000000..8fc9a94 --- /dev/null +++ b/permissions.py @@ -0,0 +1,251 @@ +# 权限处理 +import subprocess +import logging +import time + +# 配置日志 +logging.basicConfig(level=logging.INFO, format="%(asctime)s - %(levelname)s: %(message)s") + +def check_device_connection(device_id: str) -> bool: + """检查设备连接状态""" + try: + check_cmd = ["adb", "-s", device_id, "shell", "getprop", "ro.product.model"] + result = subprocess.run(check_cmd, capture_output=True, text=True, timeout=10) + if result.returncode == 0: + logging.info(f"设备 {device_id} 连接正常,型号: {result.stdout.strip()}") + return True + else: + logging.error(f"设备 {device_id} 连接失败: {result.stderr.strip()}") + return False + except subprocess.TimeoutExpired: + logging.error(f"设备 {device_id} 连接超时") + return False + except Exception as e: + logging.error(f"检查设备 {device_id} 时发生错误: {str(e)}") + return False + +def is_package_installed(device_id: str, package_name: str) -> bool: + """检查包是否已安装""" + try: + check_cmd = ["adb", "-s", device_id, "shell", "pm", "list", "packages", package_name] + result = subprocess.run(check_cmd, capture_output=True, text=True, timeout=10) + return result.returncode == 0 and package_name in result.stdout + except Exception as e: + logging.error(f"检查包 {package_name} 时发生错误: {str(e)}") + return False + +def grant_single_permission(device_id: str, package: str, permission: str) -> bool: + """ + 为单个包授予单个权限 + :return: 是否成功授予 + """ + try: + grant_cmd = [ + "adb", "-s", device_id, + "shell", "pm", "grant", package, + permission + ] + result = subprocess.run(grant_cmd, capture_output=True, text=True, timeout=15) + + if result.returncode == 0: + logging.info(f"设备 {device_id}:已成功授予 {package} 权限: {permission}") + return True + else: + error_msg = result.stderr.strip() + logging.warning(f"设备 {device_id}:授予 {package} 权限 {permission} 失败: {error_msg}") + + # 尝试使用root权限 + if "security" in error_msg.lower() or "permission" in error_msg.lower(): + logging.info(f"设备 {device_id}:尝试使用root权限授予 {package} 权限") + + # 重启adb为root模式 + root_cmd = ["adb", "-s", device_id, "root"] + subprocess.run(root_cmd, capture_output=True, text=True, timeout=10) + time.sleep(2) # 等待root权限生效 + + # 再次尝试授予权限 + result = subprocess.run(grant_cmd, capture_output=True, text=True, timeout=15) + if result.returncode == 0: + logging.info(f"设备 {device_id}:使用root权限成功授予 {package} 权限: {permission}") + return True + else: + logging.error(f"设备 {device_id}:即使使用root权限也无法授予 {package} 权限 {permission}: {result.stderr.strip()}") + return False + else: + return False + + except subprocess.CalledProcessError as e: + logging.error(f"设备 {device_id}:ADB 命令执行失败,返回码 {e.returncode}") + logging.error(f"标准输出:{e.stdout.strip()}") + logging.error(f"错误输出:{e.stderr.strip()}") + return False + except Exception as e: + logging.error(f"设备 {device_id}:处理 {package} 时发生未知错误:{str(e)}") + return False + +def grant_appium_permissions(device_id: str, require_all: bool = False) -> bool: + """ + 为 Appium UiAutomator2 服务授予权限 + :param device_id: 设备 ID + :param require_all: 是否要求所有权限都成功授予 + :return: 权限授予是否成功(根据require_all参数判断) + """ + # 首先检查设备连接 + if not check_device_connection(device_id): + return False + + packages_to_grant = [ + "io.appium.settings", + "io.appium.uiautomator2.server", + "io.appium.uiautomator2.server.test" + ] + + # 权限列表(按优先级排序) + permissions_to_grant = [ + "android.permission.WRITE_SECURE_SETTINGS", + "android.permission.CHANGE_CONFIGURATION", + "android.permission.DUMP", + ] + + success_count = 0 + total_attempted = 0 + package_results = {} + + # 检查并授予权限 + for package in packages_to_grant: + package_results[package] = {"installed": False, "permissions": {}} + + if not is_package_installed(device_id, package): + logging.warning(f"设备 {device_id}:包 {package} 未安装,跳过权限授予") + package_results[package]["installed"] = False + continue + + package_results[package]["installed"] = True + package_success = 0 + package_attempted = 0 + + for permission in permissions_to_grant: + total_attempted += 1 + package_attempted += 1 + + result = grant_single_permission(device_id, package, permission) + package_results[package]["permissions"][permission] = result + + if result: + success_count += 1 + package_success += 1 + + # 记录每个包的授权结果 + logging.info(f"设备 {device_id}:包 {package} 权限授予结果: {package_success}/{package_attempted}") + + # 统计和报告 + logging.info(f"设备 {device_id}:权限授予完成") + logging.info(f"总计: 尝试 {total_attempted} 次,成功 {success_count} 次") + + # 检查每个包的关键权限 + critical_permission = "android.permission.WRITE_SECURE_SETTINGS" + critical_failures = [] + + for package, info in package_results.items(): + if info["installed"] and critical_permission in info["permissions"]: + if not info["permissions"][critical_permission]: + critical_failures.append(package) + + if critical_failures: + logging.warning(f"设备 {device_id}:以下包的关键权限 {critical_permission} 授予失败: {', '.join(critical_failures)}") + logging.warning("这可能会影响某些自动化功能,但基础测试通常不受影响") + + # 根据require_all参数返回结果 + if require_all: + # 要求所有权限都成功 + if critical_failures: + logging.error("关键权限授予失败,无法继续(require_all=True)") + return False + return success_count == total_attempted + else: + # 不要求所有权限,只要设备连接正常就返回True + logging.info(f"设备 {device_id}:权限授予过程完成,建议重启设备或Appium服务使更改生效") + return True + +def check_appium_compatibility(device_id: str) -> dict: + """ + 检查Appium兼容性 + :return: 兼容性报告字典 + """ + try: + # 获取Android版本 + version_cmd = ["adb", "-s", device_id, "shell", "getprop", "ro.build.version.release"] + result = subprocess.run(version_cmd, capture_output=True, text=True, timeout=10) + android_version = result.stdout.strip() if result.returncode == 0 else "未知" + + report = { + "device_id": device_id, + "android_version": android_version, + "compatibility": "unknown", + "notes": [], + "suggestions": [] + } + + try: + version_num = float(android_version.split('.')[0]) + + if version_num >= 11: + report["compatibility"] = "limited" + report["notes"].append("Android 11+ 对WRITE_SECURE_SETTINGS权限限制非常严格") + report["suggestions"].append("使用--no-reset参数启动Appium") + report["suggestions"].append("设置autoGrantPermissions=false") + + elif version_num >= 10: + report["compatibility"] = "moderate" + report["notes"].append("Android 10 限制了WRITE_SECURE_SETTINGS权限") + report["suggestions"].append("可尝试使用root权限的设备") + + elif version_num >= 9: + report["compatibility"] = "good" + report["notes"].append("Android 9 兼容性较好") + + else: + report["compatibility"] = "excellent" + report["notes"].append("Android 8或以下版本完全兼容") + + except (ValueError, IndexError): + report["notes"].append("无法解析Android版本") + + return report + + except Exception as e: + logging.error(f"检查兼容性时出错: {str(e)}") + return {"device_id": device_id, "error": str(e)} + +# 使用示例 +if __name__ == "__main__": + # 获取设备ID(示例) + devices_cmd = ["adb", "devices"] + result = subprocess.run(devices_cmd, capture_output=True, text=True) + + if result.returncode == 0: + lines = result.stdout.strip().split('\n')[1:] # 跳过第一行标题 + for line in lines: + if line.strip() and "device" in line: + device_id = line.split('\t')[0] + logging.info(f"找到设备: {device_id}") + + # 1. 检查兼容性 + report = check_appium_compatibility(device_id) + logging.info(f"兼容性报告: Android {report.get('android_version', '未知')} - {report.get('compatibility', '未知')}") + + # 2. 授予权限(不要求全部成功) + success = grant_appium_permissions(device_id, require_all=False) + + if success: + logging.info(f"设备 {device_id} 设置完成,可以开始测试") + else: + logging.warning(f"设备 {device_id} 权限授予有失败,但可能仍可进行基础测试") + + # 3. 提供建议 + if "suggestions" in report: + logging.info("建议:") + for suggestion in report["suggestions"]: + logging.info(f" - {suggestion}") + else: + logging.error("无法获取设备列表,请确保ADB已正确安装且设备已连接") \ No newline at end of file diff --git a/test_results/上传失败的断点.txt b/test_results/上传失败的断点.txt new file mode 100644 index 0000000..62e0a65 --- /dev/null +++ b/test_results/上传失败的断点.txt @@ -0,0 +1,5 @@ +CDWZQ-3标-雷庙村大桥-8-号桥台-平原 +CDWZQ-3标-雷庙村特大桥-5-6-号墩-平原 +CDWZQ-3标-金马村特大桥-14-号墩-平原 +CDWZQ-3标-雷庙村特大桥-4#-7-14号墩-平原 +CDWZQ-3标-老屋坡特大桥-14-21-平原 diff --git a/test_results/上传成功的断点.txt b/test_results/上传成功的断点.txt new file mode 100644 index 0000000..e69de29 diff --git a/上传人员信息.xlsx b/上传人员信息.xlsx new file mode 100644 index 0000000000000000000000000000000000000000..4baf13e44244e9cfd643e3c9141d8ab98313f0fb GIT binary patch literal 24659 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 L6o3C^^6S3=zDG^D literal 0 HcmV?d00001