├── .idea
├── encodings.xml
├── inspectionProfiles
│ └── Project_Default.xml
├── misc.xml
├── modules.xml
├── uitest-server.iml
└── workspace.xml
├── .pytest_cache
└── v
│ └── cache
│ ├── lastfailed
│ └── nodeids
├── README.md
├── Report
├── ExampleReport.html
├── TestResultReport.html
├── cov_profiler.html
└── covdata.cov
├── __pycache__
├── case.cpython-37.pyc
├── config.cpython-37.pyc
├── elementdb.cpython-37.pyc
├── function.cpython-37.pyc
├── interpret.cpython-37.pyc
├── keyworddb.cpython-37.pyc
├── profiler.cpython-37.pyc
├── test.cpython-37.pyc
└── testcase.cpython-37.pyc
├── case.py
├── case.pyc
├── config.py
├── elementdb.py
├── elementdb.pyc
├── function.py
├── function.pyc
├── interpret.py
├── keyworddb.py
├── keyworddb.pyc
├── process.py
├── profiler.py
├── test.html
├── test.js
├── test_other.js
└── testcase.py
/.idea/encodings.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
--------------------------------------------------------------------------------
/.idea/inspectionProfiles/Project_Default.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
13 |
14 |
15 |
--------------------------------------------------------------------------------
/.idea/misc.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 | ApexVCS
5 |
6 |
7 |
--------------------------------------------------------------------------------
/.idea/modules.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
--------------------------------------------------------------------------------
/.idea/uitest-server.iml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
10 |
11 |
12 |
--------------------------------------------------------------------------------
/.idea/workspace.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
16 |
17 |
18 |
19 |
20 |
21 |
22 |
23 |
24 |
25 |
26 |
27 |
28 |
29 |
30 |
31 |
32 |
33 |
34 |
35 |
36 |
37 |
38 |
39 |
40 |
41 |
42 |
43 |
44 |
45 |
46 |
47 |
48 |
49 |
50 |
51 |
52 |
53 |
54 |
55 |
56 |
57 |
58 |
59 |
60 |
61 |
62 |
63 |
64 |
65 |
66 |
67 |
68 |
69 |
70 |
71 |
72 |
73 |
74 |
75 |
76 |
77 |
78 |
79 |
80 |
81 |
82 |
83 |
84 |
85 |
86 |
87 |
88 |
89 |
90 |
91 |
92 |
93 |
94 |
95 |
96 |
97 |
98 |
99 |
100 |
101 |
102 |
107 |
108 |
109 |
110 | div_id
111 | showTestDetail
112 | HTML_TMPL
113 | chart_script
114 | stream
115 | showOutput
116 | font style="background:yellow
117 | hahahahah
118 | hahaha
119 | 17
120 | 999
121 | push.apply
122 | Chrome
123 | applyToTag
124 | cb
125 | push.2y7i._typeof
126 | wewewewewewewewewew
127 | wocao
128 | {'scriptId': '17'
129 | 'scriptId': '17'
130 | requestId
131 | response
132 |
133 |
134 |
135 |
136 |
137 |
138 |
139 |
140 |
141 |
142 |
143 |
144 |
145 |
146 |
147 |
148 |
149 |
150 |
151 |
152 |
153 |
154 |
155 |
156 |
157 |
158 |
159 |
160 |
161 |
162 |
163 |
164 |
165 |
166 |
167 |
168 |
169 |
170 |
171 |
172 |
173 |
174 | true
175 | DEFINITION_ORDER
176 |
177 |
178 |
179 |
180 |
181 |
182 |
183 |
184 |
185 |
186 |
187 |
188 |
189 |
190 |
191 |
192 |
193 |
194 |
195 |
196 |
197 |
198 |
199 |
200 |
201 |
202 |
203 |
204 |
205 |
206 |
207 |
208 |
209 |
210 |
211 |
212 |
213 |
214 |
215 |
216 |
217 |
218 |
219 |
220 |
221 |
222 |
223 |
224 |
225 |
226 |
227 |
228 |
229 |
230 |
231 |
232 |
233 |
234 |
235 | project
236 |
237 |
238 |
239 |
240 |
241 |
242 |
243 |
244 |
245 |
246 |
247 |
248 |
249 |
250 |
251 |
252 |
253 |
254 |
255 |
256 |
257 |
258 |
259 |
260 |
261 |
262 |
263 |
264 |
265 |
266 |
267 |
268 |
269 |
270 |
271 |
272 |
273 |
274 |
275 |
276 |
277 |
278 |
279 |
280 |
281 |
282 |
283 |
284 |
285 |
286 |
287 |
288 |
289 |
290 |
291 |
292 |
293 |
294 |
295 |
296 |
297 |
298 |
299 |
300 |
301 |
302 |
303 |
304 |
305 |
306 |
307 |
308 |
309 |
310 |
311 |
312 |
313 |
314 |
315 |
316 |
317 |
318 |
319 |
320 |
321 |
322 |
323 |
324 |
325 |
326 |
327 |
328 |
329 |
330 |
331 |
332 |
333 |
334 |
335 |
336 |
337 |
338 |
339 |
340 |
341 |
342 |
343 |
344 |
345 |
346 |
347 |
348 |
349 |
350 |
351 |
352 |
353 |
354 |
355 |
356 |
357 |
358 |
359 |
360 |
361 |
362 |
363 |
364 |
365 |
366 |
367 |
368 |
369 |
370 |
371 |
372 |
373 |
374 |
375 |
376 |
377 |
378 |
379 |
380 |
381 |
382 |
383 |
384 |
385 |
386 |
387 |
388 |
389 |
390 |
391 |
392 |
393 |
394 |
395 |
396 |
397 |
398 |
399 |
400 |
401 |
402 |
403 |
404 |
405 |
406 |
407 |
408 |
409 |
410 |
411 |
412 |
413 |
414 |
415 |
416 |
417 |
418 |
419 |
420 |
421 |
422 |
423 |
424 |
425 |
426 |
427 |
428 |
429 |
430 |
431 |
432 |
433 |
434 |
435 |
436 |
437 |
438 |
439 |
440 |
441 |
442 |
443 |
444 |
445 |
446 |
447 |
448 |
449 |
450 |
451 |
452 |
453 |
454 |
455 |
456 |
457 |
458 |
459 |
460 |
461 |
462 |
463 |
464 |
465 |
466 |
467 |
468 |
469 |
470 |
471 |
472 |
473 |
474 |
475 |
476 |
477 |
478 |
479 |
480 |
481 |
482 |
483 |
484 |
485 |
486 |
487 |
488 |
489 |
490 |
491 |
492 |
493 |
494 |
495 |
496 |
497 |
498 |
499 |
500 |
501 |
502 |
503 |
504 |
505 |
506 |
507 |
508 |
509 |
510 |
511 |
512 |
513 |
514 |
515 |
516 |
517 |
518 |
519 |
520 |
521 |
522 |
523 |
524 |
525 |
526 |
527 |
528 |
529 |
530 |
531 |
532 |
533 |
534 |
535 |
536 |
537 |
538 |
539 |
540 |
541 |
542 |
543 |
544 |
545 |
546 |
547 |
548 |
549 |
550 |
551 |
552 |
553 |
554 |
555 |
556 |
557 |
558 |
559 |
560 |
561 |
562 |
563 | 1531376984041
564 |
565 |
566 | 1531376984041
567 |
568 |
569 |
570 |
571 |
572 |
573 |
574 |
575 |
576 |
577 |
578 |
579 |
580 |
581 |
582 |
583 |
584 |
585 |
586 |
587 |
588 |
589 |
590 |
591 |
592 |
593 |
594 |
595 |
596 |
597 |
598 |
599 |
600 |
601 |
602 |
603 |
604 |
605 |
606 |
607 |
608 |
609 |
610 |
611 |
612 |
613 |
614 |
615 |
616 |
617 |
618 |
619 |
620 |
621 |
622 |
623 |
624 |
625 |
626 |
627 |
628 |
629 |
630 |
631 |
632 |
633 |
634 |
635 |
636 |
637 |
638 |
639 |
640 |
641 |
642 |
643 |
644 |
645 |
646 |
647 |
648 |
649 |
650 |
651 |
652 |
653 |
654 |
655 |
656 | file://$USER_HOME$/AppData/Local/Programs/Python/Python37/Lib/HTMLTestRunner_PY3.py
657 | 804
658 |
659 |
660 |
661 |
662 |
663 |
664 |
665 |
666 |
667 |
668 |
669 |
670 |
671 |
672 |
673 |
674 |
675 |
676 |
677 |
678 |
679 |
680 |
681 |
682 |
683 |
684 |
685 |
686 |
687 |
688 |
689 |
690 |
691 |
692 |
693 |
694 |
695 |
696 |
697 |
698 |
699 |
700 |
701 |
702 |
703 |
704 |
705 |
706 |
707 |
708 |
709 |
710 |
711 |
712 |
713 |
714 |
715 |
716 |
717 |
718 |
719 |
720 |
721 |
722 |
723 |
724 |
725 |
726 |
727 |
728 |
729 |
730 |
731 |
732 |
733 |
734 |
735 |
736 |
737 |
738 |
739 |
740 |
741 |
742 |
743 |
744 |
745 |
746 |
747 |
748 |
749 |
750 |
751 |
752 |
753 |
754 |
755 |
756 |
757 |
758 |
759 |
760 |
761 |
762 |
763 |
764 |
765 |
766 |
767 |
768 |
769 |
770 |
771 |
772 |
773 |
774 |
775 |
776 |
777 |
778 |
779 |
780 |
781 |
782 |
783 |
784 |
785 |
786 |
787 |
788 |
789 |
790 |
791 |
792 |
793 |
794 |
795 |
796 |
797 |
798 |
799 |
800 |
801 |
802 |
803 |
804 |
805 |
806 |
807 |
808 |
809 |
810 |
811 |
812 |
813 |
814 |
815 |
816 |
817 |
818 |
819 |
820 |
821 |
822 |
823 |
824 |
825 |
826 |
827 |
828 |
829 |
830 |
831 |
832 |
833 |
834 |
835 |
836 |
837 |
838 |
839 |
840 |
841 |
842 |
843 |
844 |
845 |
846 |
847 |
848 |
849 |
850 |
851 |
852 |
853 |
854 |
855 |
856 |
857 |
858 |
859 |
860 |
861 |
862 |
863 |
864 |
865 |
866 |
867 |
868 |
869 |
870 |
871 |
872 |
873 |
874 |
875 |
876 |
877 |
878 |
879 |
880 |
881 |
882 |
883 |
884 |
885 |
886 |
887 |
888 |
889 |
890 |
891 |
892 |
893 |
894 |
895 |
896 |
897 |
898 |
899 |
900 |
901 |
902 |
903 |
904 |
905 |
906 |
907 |
908 |
909 |
910 |
911 |
912 |
913 |
914 |
915 |
916 |
917 |
918 |
919 |
920 |
921 |
922 |
923 |
924 |
925 |
926 |
927 |
928 |
929 |
930 |
931 |
932 |
933 |
934 |
935 |
936 |
937 |
938 |
939 |
940 |
941 |
942 |
943 |
944 |
945 |
946 |
947 |
948 |
949 |
950 |
951 |
952 |
953 |
954 |
955 |
956 |
957 |
958 |
959 |
960 |
961 |
962 |
963 |
964 |
965 |
966 |
967 |
968 |
969 |
970 |
971 |
972 |
973 |
974 |
975 |
976 |
977 |
978 |
979 |
980 |
981 |
982 |
983 |
984 |
985 |
986 |
987 |
988 |
989 |
990 |
991 |
992 |
993 |
994 |
995 |
996 |
997 |
998 |
999 |
1000 |
1001 |
1002 |
1003 |
1004 |
1005 |
1006 |
1007 |
1008 |
1009 |
1010 |
1011 |
1012 |
1013 |
1014 |
1015 |
1016 |
1017 |
1018 |
1019 |
1020 |
1021 |
1022 |
1023 |
1024 |
1025 |
1026 |
1027 |
1028 |
1029 |
1030 |
1031 |
1032 |
1033 |
1034 |
1035 |
1036 |
1037 |
1038 |
1039 |
1040 |
1041 |
1042 |
1043 |
1044 |
1045 |
1046 |
1047 |
1048 |
1049 |
1050 |
1051 |
1052 |
1053 |
1054 |
1055 |
1056 |
1057 |
1058 |
1059 |
1060 |
1061 |
1062 |
1063 |
1064 |
1065 |
1066 |
1067 |
1068 |
1069 |
1070 |
1071 |
1072 |
1073 |
1074 |
1075 |
1076 |
1077 |
1078 |
1079 |
1080 |
1081 |
1082 |
1083 |
1084 |
1085 |
1086 |
1087 |
1088 |
1089 |
1090 |
1091 |
1092 |
1093 |
1094 |
1095 |
--------------------------------------------------------------------------------
/.pytest_cache/v/cache/lastfailed:
--------------------------------------------------------------------------------
1 | {
2 | "pytest4.py::test_ehlo": true,
3 | "test4.py": true
4 | }
--------------------------------------------------------------------------------
/.pytest_cache/v/cache/nodeids:
--------------------------------------------------------------------------------
1 | []
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | # uitest-keyword
2 | ## 一些说明
3 | 这个demo仅仅是个思路指引,当然你也可以直接用,不过还有很多没有完善和很死板的地方需要去修补,之后ok了,我会再放上用这个框架为核心打造的web平台版。
4 |
5 | 想运行这个demon,你需要:
6 | py3的环境
7 | selenium库
8 | HTMLTestRunner_PY3(放到py3目录下的lib目录里)
9 | 适合你chrome版本的chromedriver(放到py3的根目录下)
10 |
11 | 在case模块编写用例,执行process模块执行用例,测试报告在report目录下。
12 |
13 | ## 该项目的说明博客:
14 | https://www.jianshu.com/p/b65c3014198c
15 |
--------------------------------------------------------------------------------
/Report/ExampleReport.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 | 用例执行报告
6 |
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
100 |
101 |
102 |
103 |
104 |
198 |
199 |
200 |
201 |
208 |
209 |
210 |
211 |
212 |
213 |
214 |
215 |
216 |
217 |
218 |
219 |
220 |
221 |
222 |
223 |
224 |
225 |
226 |
227 |
235 |
236 |
237 | testcase.TEST_BAIDU |
238 | 1 |
239 | 1 |
240 | 0 |
241 | 0 |
242 | 详情 |
243 |
244 |
245 |
246 | test_baidu |
247 | 通过 |
248 |
249 |
250 |
251 | 总计 |
252 | 1 |
253 | 1 |
254 | 0 |
255 | 0 |
256 | |
257 |
258 |
259 |
260 |
261 |
262 |
307 |
308 |
309 |
310 |
311 |
--------------------------------------------------------------------------------
/Report/TestResultReport.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 | 用例执行报告
6 |
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
100 |
101 |
102 |
103 |
104 |
198 |
199 |
200 |
201 |
208 |
209 |
210 |
211 |
212 |
213 |
214 |
215 |
216 |
217 |
218 |
219 |
220 |
221 |
222 |
223 |
224 |
225 |
226 |
227 |
235 |
236 |
237 | testcase.TEST_BAIDU |
238 | 1 |
239 | 1 |
240 | 0 |
241 | 0 |
242 | 详情 |
243 |
244 |
245 |
246 | test_baidu |
247 | 通过 |
248 |
249 |
250 |
251 | 总计 |
252 | 1 |
253 | 1 |
254 | 0 |
255 | 0 |
256 | |
257 |
258 |
259 |
260 |
261 |
262 |
307 |
308 |
309 |
310 |
311 |
--------------------------------------------------------------------------------
/Report/cov_profiler.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
20 |
27 |
28 |
29 | function fa(){
30 | document.getElementById('H').innerHTML="点击了A,触发了A方法"
31 | };
32 | function fb(){
33 | document.getElementById('H').innerHTML="点击了B,触发了B方法"
34 | };
35 | function fc(){
36 | document.getElementById('H').innerHTML="点击了C,触发了C方法"
37 | }
38 |
39 |
40 | function fd(){
41 | document.getElementById('H').innerHTML="点击了D,触发了D方法,D在另一个js里"
42 | };
43 |
44 |
45 |
46 |
47 |
48 |
49 |
50 |
--------------------------------------------------------------------------------
/Report/covdata.cov:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/icesword0760/uitest-keyword/b11dd06bf9da8ada77e9632c477a8035cae095bb/Report/covdata.cov
--------------------------------------------------------------------------------
/__pycache__/case.cpython-37.pyc:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/icesword0760/uitest-keyword/b11dd06bf9da8ada77e9632c477a8035cae095bb/__pycache__/case.cpython-37.pyc
--------------------------------------------------------------------------------
/__pycache__/config.cpython-37.pyc:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/icesword0760/uitest-keyword/b11dd06bf9da8ada77e9632c477a8035cae095bb/__pycache__/config.cpython-37.pyc
--------------------------------------------------------------------------------
/__pycache__/elementdb.cpython-37.pyc:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/icesword0760/uitest-keyword/b11dd06bf9da8ada77e9632c477a8035cae095bb/__pycache__/elementdb.cpython-37.pyc
--------------------------------------------------------------------------------
/__pycache__/function.cpython-37.pyc:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/icesword0760/uitest-keyword/b11dd06bf9da8ada77e9632c477a8035cae095bb/__pycache__/function.cpython-37.pyc
--------------------------------------------------------------------------------
/__pycache__/interpret.cpython-37.pyc:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/icesword0760/uitest-keyword/b11dd06bf9da8ada77e9632c477a8035cae095bb/__pycache__/interpret.cpython-37.pyc
--------------------------------------------------------------------------------
/__pycache__/keyworddb.cpython-37.pyc:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/icesword0760/uitest-keyword/b11dd06bf9da8ada77e9632c477a8035cae095bb/__pycache__/keyworddb.cpython-37.pyc
--------------------------------------------------------------------------------
/__pycache__/profiler.cpython-37.pyc:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/icesword0760/uitest-keyword/b11dd06bf9da8ada77e9632c477a8035cae095bb/__pycache__/profiler.cpython-37.pyc
--------------------------------------------------------------------------------
/__pycache__/test.cpython-37.pyc:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/icesword0760/uitest-keyword/b11dd06bf9da8ada77e9632c477a8035cae095bb/__pycache__/test.cpython-37.pyc
--------------------------------------------------------------------------------
/__pycache__/testcase.cpython-37.pyc:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/icesword0760/uitest-keyword/b11dd06bf9da8ada77e9632c477a8035cae095bb/__pycache__/testcase.cpython-37.pyc
--------------------------------------------------------------------------------
/case.py:
--------------------------------------------------------------------------------
1 | cases =[
2 | [
3 | {'name': 'test_baidu'},
4 | {'action': "原生打开网址", 'parmeters':['file:///C:/Users/zyj/PycharmProjects/uitest-server/test.html']},
5 | {'action': "等待", 'parmeters': [2]},
6 | {'action':"点击", 'parmeters':["按钮A"]},
7 | #{'action':"点击", 'parmeters':["按钮B"]},
8 | {'action': "等待", 'parmeters': [1]},
9 | #{'action':"点击", 'parmeters':["按钮C"]},
10 | #{'action': "等待", 'parmeters': [1]},
11 | #{'action':"点击", 'parmeters':["按钮D"]},
12 |
13 | #{'action': "点击", 'parmeters': ["百度搜索按钮"]},
14 | #{'validate': "页面文本包含", 'parmeters': ["怪不开机"]},
15 | ],
16 | #[
17 | # {'name': 'test_baidu222'},
18 | # {'action': "原生打开网址", 'parmeters':['https://www.baidu.com/']},
19 | #{'action':"输入", 'parmeters':["百度搜索框",'100行代码打造关键字驱动的ui自动化测试框架']},
20 | #{'action': "点击", 'parmeters': ["百度搜索按钮"]},
21 | #{'validate': "页面文本包含", 'parmeters': ["test"]},
22 | #],
23 | ]
24 |
--------------------------------------------------------------------------------
/case.pyc:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/icesword0760/uitest-keyword/b11dd06bf9da8ada77e9632c477a8035cae095bb/case.pyc
--------------------------------------------------------------------------------
/config.py:
--------------------------------------------------------------------------------
1 | chrome_install_path = r"C:\Users\zyj\AppData\Local\Google\Chrome SxS\Application" #你的chrome所在的地址
2 | cov_report_path = r"C:\Users\zyj\PycharmProjects\uitest-server\Report\cov_profiler.html" #生成的覆盖率报告的地址
3 | cov_data_path = r"C:\Users\zyj\PycharmProjects\uitest-server\Report\covdata.cov" #生成的覆盖率文件的地址
--------------------------------------------------------------------------------
/elementdb.py:
--------------------------------------------------------------------------------
1 |
2 | element = {
3 | '百度搜索框':('By.ID', 'kw'),
4 | '百度搜索按钮': ('By.ID', 'su'),
5 | '按钮A': ('By.ID','A'),
6 | '按钮B': ('By.ID','B'),
7 | '按钮C': ('By.ID','C'),
8 | '按钮D': ('By.ID','D'),
9 | }
--------------------------------------------------------------------------------
/elementdb.pyc:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/icesword0760/uitest-keyword/b11dd06bf9da8ada77e9632c477a8035cae095bb/elementdb.pyc
--------------------------------------------------------------------------------
/function.py:
--------------------------------------------------------------------------------
1 | # -*- coding: utf-8 -*-
2 | import time
3 | import unittest
4 |
5 | def input(element,str,driver):
6 | find_element(element, driver).send_keys(str)
7 |
8 | def click(element,driver):
9 | find_element(element,driver).click()
10 |
11 | def find_element(element,driver):
12 | webelement = driver.find_element(*element)
13 | return webelement
14 |
15 | def switch_to_last_handles(driver):
16 | """
17 | 在打开的窗口里选择最后一个
18 | :return:None
19 | """
20 | all_handles = driver.window_handles
21 | driver.switch_to_window(all_handles[-1])
22 |
23 | def switch_to_another_hanles(now_handle,driver):
24 | """
25 | 只适用于打开两个窗口的情况,传入现在的窗口句柄后,选择另一个窗口
26 | :param now_handle:现在的窗口句柄
27 | :return:
28 | """
29 | all_handles = driver.window_handles # 得到当前开启的所有窗口的句柄
30 | for handle in all_handles:
31 | if handle != now_handle: # 获取到与当前窗口不一样的窗口
32 | driver.switch_to_window(handle)
33 |
34 | def open_url(url,driver):
35 | driver.get(url)
36 |
37 | def navigate_url(url,chrome):
38 | chrome.Page.navigate(url=url)
39 |
40 | def wait_time(num):
41 | time.sleep(num)
42 |
43 | def element_text_has(element,str,driver):
44 | element = find_element(element, driver)
45 | element_text = element.text
46 | return unittest.TestCase().assertIn(str,element_text,"元素文本不包含预期值!")
47 |
48 | def page_text_has(str,driver,chrome):
49 | chrome.wait_event("Page.loadEventFired", timeout=60)
50 | page_text = driver.page_source
51 | return unittest.TestCase().assertIn(str,page_text,"页面文本不包含预期值!")
52 |
53 | def switch_to_iframe(iframe_msg,driver):
54 | driver.switch_to.frame(iframe_msg)
55 |
56 | def switch_to_default_content(driver):
57 | driver.switch_to_default_content()
--------------------------------------------------------------------------------
/function.pyc:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/icesword0760/uitest-keyword/b11dd06bf9da8ada77e9632c477a8035cae095bb/function.pyc
--------------------------------------------------------------------------------
/interpret.py:
--------------------------------------------------------------------------------
1 | from selenium.webdriver.common.by import By
2 | from case import cases
3 | from elementdb import element
4 | from keyworddb import keyword
5 | from selenium import webdriver
6 | import function
7 | import inspect
8 |
9 | class Interpret(object):
10 | def __init__(self):
11 | self.casename = ''
12 | self.action_list = []
13 | self.testcases = []
14 | self.testcase = {
15 | 'casename': self.casename,
16 | 'actions': self.action_list,
17 | }
18 | self.interpret_cases()
19 |
20 | def init_context(self):
21 | self.casename = ''
22 | self.action_list = []
23 | self.testcase = {
24 | 'casename': self.casename,
25 | 'actions': self.action_list,
26 | }
27 |
28 | def interpret_cases(self):
29 | for case in cases:
30 | self.interpret_case(case)
31 | self.bind_testcase(self.testcase)
32 | self.init_context()
33 |
34 | def interpret_case(self, case):
35 | for _ in case:
36 | for k,v in _.items():
37 | if k == 'name':
38 | self.bind_casename(v)
39 | if k == 'action' or k == 'validate':
40 | func_name = keyword[v]
41 | func = getattr(function, func_name)
42 | func_parmeters = inspect.signature(func).parameters.values()#func_parmeters是获取到的函数的参数列表
43 | parmeter_list = self.interpret_parmeters(_['parmeters'],func_parmeters)
44 | action = {func:parmeter_list}
45 | self.bind_action_list(action)
46 |
47 | def interpret_parmeters(self, parmeters, func_parmeters):
48 | parmeter_list = []
49 | for parmeter in parmeters:
50 | parmeter_list.append(parmeter)
51 | # 遍历函数的参数名称集合,如果其中有element,则将该参数通过element映射关系,转换为元组数据并替换原有用例对应的参数
52 | for index, f_type in enumerate(func_parmeters):
53 | f_type = str(f_type)
54 | if f_type == 'element':
55 | element_keyword = parmeters[0]
56 | find_method = eval(element[element_keyword][0])
57 | # element_tuple为元组数据,记录了对象的查找方式和位置信息
58 | element_tuple = (find_method, element[element_keyword][1])
59 | parmeter_list[index] = element_tuple
60 | if f_type == 'driver':
61 | #print(type(self.driver))
62 | #parmeter_list[index] = '$Web_Driver'
63 | parmeter_list.append('$Web_Driver')
64 | #parmeter_list.append(self.driver)
65 | if f_type == 'chrome':
66 | parmeter_list.append('$Chrome_Dev')
67 | return parmeter_list
68 |
69 | @classmethod
70 | def replace_driver(self, actions, driver):
71 | for action in actions:
72 | for v in action.values():
73 | for index, _ in enumerate(v):
74 | if _ == '$Web_Driver':
75 | v[index] = driver
76 |
77 | @classmethod
78 | def replace_chrome(self, actions, chrome):
79 | for action in actions:
80 | for v in action.values():
81 | for index, _ in enumerate(v):
82 | if _ == '$Chrome_Dev':
83 | v[index] = chrome
84 |
85 | def bind_action_list(self, action):
86 | self.testcase['actions'].append(action)
87 |
88 | def bind_casename(self, name):
89 | self.testcase['casename'] = name
90 |
91 | def bind_testcase(self, case):
92 | self.testcases.append(case)
93 |
94 |
95 |
--------------------------------------------------------------------------------
/keyworddb.py:
--------------------------------------------------------------------------------
1 | keyword = {
2 | "输入": 'input',
3 | "点击": 'click',
4 | "打开网址": 'open_url',
5 | "等待":'wait_time',
6 | "元素文本包含": 'element_text_has',
7 | "页面文本包含": 'page_text_has',
8 | "原生打开网址": 'navigate_url',
9 | "切换至最后一个窗口": 'switch_to_last_handles',
10 | "切换至另一个窗口": 'switch_to_another_hanles',
11 | "切换iframe":'switch_to_iframe',#入参是iframe的id或者name,str类型
12 | "切换回默认环境":'switch_to_default_content',#在iframe操作完毕后,需要切换回默认环境
13 | }
--------------------------------------------------------------------------------
/keyworddb.pyc:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/icesword0760/uitest-keyword/b11dd06bf9da8ada77e9632c477a8035cae095bb/keyworddb.pyc
--------------------------------------------------------------------------------
/process.py:
--------------------------------------------------------------------------------
1 | # -*- coding: utf-8 -*-
2 | import unittest
3 | import os
4 | from HTMLTestRunner_PY3 import HTMLTestRunner
5 | from testcase import TestCase
6 |
7 | def process_case():
8 | report_title = '用例执行报告'
9 | desc = '测试执行情况展示'
10 | report_file = r".\Report\TestResultReport.html"
11 | suite = unittest.TestSuite()
12 | suite.addTests(TestCase().testcases)
13 | print(suite)
14 | with open(report_file, 'wb') as report:
15 | runner = HTMLTestRunner(stream=report, title=report_title, description=desc)
16 | runner.run(suite)
17 |
18 | #runner = unittest.TextTestRunner()
19 | #runner.run(suite)
20 |
21 |
22 | process_case()
--------------------------------------------------------------------------------
/profiler.py:
--------------------------------------------------------------------------------
1 | import test
2 | import requests
3 | import copy
4 | import pickle
5 |
6 | class ProfilerResultTemplet(object):
7 | """
8 | 模板类,主要放置生成覆盖率网页的模板
9 | """
10 |
11 | MINTEMPLET = r"""
12 |
13 |
14 |
15 |
16 |
17 |
30 |
31 |
32 | js名称 |
33 |
34 |
35 | %(jslist)s
36 |
37 |
38 | %(pre)s
39 |
40 |
41 |
42 |
43 |
44 |
45 | """
46 | JSLISTTEMPLET = r""" %(js_name)s |
"""
47 |
48 | CODETEMPLET = r"""
49 | %(code_data)s
50 |
"""
51 |
52 | class Profiler(ProfilerResultTemplet):
53 |
54 | def merge_same_func_ranges(self, covdata):
55 | """
56 | 当一个js的覆盖率数据包含多个相同的被统计函数时,合并这些被统计函数的覆盖率数据(ranges)
57 | :param covdata:过滤掉不需要域名和已覆盖函数的覆盖率数据
58 | :return: covdata:合并完的统计数据,该数据不会存在一个js文件含有多个相同名称函数的情况了
59 | """
60 | copy_cov = copy.deepcopy(covdata)
61 | for index,data in enumerate(covdata):
62 | for function in data['functions']:
63 | functionName = function['functionName']
64 | function_ranges = [func['ranges'] for func in copy_cov[index]['functions'] if functionName == func['functionName']]
65 | if len(function_ranges) > 1:
66 | function_ranges = [range for ranges in function_ranges for range in ranges]
67 | ranges = self.by_rule_merge_ranges(function_ranges)
68 | function_item = {
69 | 'functionName': functionName,
70 | 'ranges': ranges,
71 | 'isBlockCoverage': False
72 | }
73 | list(map(lambda func: copy_cov[index]['functions'].remove(func),[func for func in covdata[index]['functions'] if functionName == func['functionName']]))
74 | copy_cov[index]['functions'].append(function_item)
75 | return copy_cov
76 |
77 | def by_rule_merge_ranges(self,list):
78 | """
79 | 当传入坐标数组时,将数组内的坐标转换为唯一坐标(即 既不包含(或者被包含)其他坐标,也不能和其他坐标连续的坐标)
80 | :param list:传入的坐标数组
81 | :return:转换后的坐标数组
82 | """
83 | merge_list = []
84 | def _merge(ranges):
85 | copy_ranges = copy.deepcopy(ranges)
86 | ranges_length = len(ranges) - 1
87 | start_offset = copy_ranges[0]['startOffset']
88 | end_offset = copy_ranges[0]['endOffset']
89 | for j in range(ranges_length):
90 | if j < ranges_length:
91 | next_start_offset = ranges[j + 1]['startOffset']
92 | next_end_offset = ranges[j + 1]['endOffset']
93 | if start_offset <= (
94 | next_end_offset + 1) and start_offset >= next_start_offset and end_offset >= next_end_offset:
95 | start_offset = next_start_offset
96 | copy_ranges.remove(ranges[j + 1])
97 | elif end_offset >= (
98 | next_start_offset - 1) and end_offset <= next_end_offset and start_offset <= next_start_offset:
99 | end_offset = next_end_offset
100 | copy_ranges.remove(ranges[j + 1])
101 | elif start_offset >= next_start_offset and end_offset <= next_end_offset:
102 | start_offset = next_start_offset
103 | end_offset = next_end_offset
104 | copy_ranges.remove(ranges[j + 1])
105 | elif start_offset <= next_start_offset and end_offset >= next_end_offset:
106 | copy_ranges.remove(ranges[j + 1])
107 | copy_ranges.remove(copy_ranges[0])
108 | merge_list.append({
109 | 'startOffset': start_offset,
110 | 'endOffset': end_offset,
111 | 'count': 0
112 | })
113 | if len(copy_ranges) == 0:
114 | return
115 | else:
116 | _merge(copy_ranges)
117 | _merge(list)
118 | return merge_list
119 |
120 | def cov_domain_filter(self, covdata, domains):
121 | """
122 | 过滤统计结果,只保留含有指定域名称的js统计结果
123 | :param covdata: 覆盖率统计结果
124 | :param domains: 指定的域名称
125 | :return: 过滤后的统计结果
126 | """
127 | covdata = covdata['result']['result']
128 | cov_filter_datas = []
129 | if domains:
130 | for data in covdata:
131 | for domain in domains:
132 | if domain in data['url']:
133 | cov_filter_datas.append(data)
134 | return cov_filter_datas
135 | else:
136 | return covdata
137 |
138 | def cov_not_count_filter(self, covdata):
139 | """
140 | 过滤统计结果,只保留未被覆盖到的函数统计结果
141 | :param covdata: cov_domain_filter()后的覆盖率统计结果
142 | :return: 过滤后的统计结果
143 | """
144 | copy_cov = copy.deepcopy(covdata)
145 | #如果是被覆盖到函数,则移除该函数的统计数据
146 | for index,data in enumerate(covdata):
147 | for f_index,function in enumerate(data['functions']):
148 | for range in function['ranges']:
149 | if range['count'] is not 0:
150 | copy_cov[index]['functions'][f_index]['ranges'].remove(range)
151 | covdata = copy.deepcopy(copy_cov)
152 | #如果某函数统计数据为空,则移除该函数
153 | for index,data in enumerate(copy_cov):
154 | for function in data['functions']:
155 | if len(function['ranges']) == 0:
156 | covdata[index]['functions'].remove(function)
157 | return covdata
158 |
159 | def code_html_filter(self, codedata):
160 | """
161 | 转义js代码里的html标签防止干扰展示
162 | :param codedata: 待转义的js代码
163 | :return: 转义后的js代码
164 | """
165 | codedata = codedata.replace("<", "<")
166 | codedata = codedata.replace(">", "<")
167 | codedata = codedata.replace("""<font style="background:yellow" bgcolor="yellow"<""", """""")
168 | codedata = codedata.replace("</font<", "")
169 | return codedata
170 |
171 | def make_jslist_tmp(self, pid, url):
172 | """
173 | 生成js列表部分的html模板填充方法
174 | :param pid: js的id
175 | :param url: js的url路径
176 | :return: 填充好的模板
177 | """
178 | jslist_tmp = self.JSLISTTEMPLET % dict(
179 | pid = pid,
180 | js_name=url
181 | )
182 | return jslist_tmp
183 |
184 | def make_codepre_tmp(self,pid,url,positions):
185 | """
186 | 生成js代码块部分的html模板填充方法
187 | :param pid: 与js的id对应的id
188 | :param url: 获取js代码用的url
189 | :param positions: 未被覆盖的js的索引位置
190 | :return: 填充好的模板
191 | """
192 | codepre_tmp = self.CODETEMPLET % dict(
193 | pid=pid,
194 | code_data=self.insert_backgroud(self.get_js(url), positions)
195 | )
196 | return codepre_tmp
197 |
198 | def make_cov_profiler_temp(self, covdata):
199 | """
200 | 生成最终的覆盖率模板的填充方法
201 | :param covdata: 覆盖率数据
202 | :return: 填充好的模板
203 | """
204 | jslists_tmp = ""
205 | codepres_tmp = ""
206 | for index,data in enumerate(covdata):
207 | if data['url'].count('.js') and len(data['functions']) > 0:
208 | url = data['url'].replace(' ', '')
209 | jslists_tmp=jslists_tmp+(self.make_jslist_tmp(index,url))
210 | positions = []
211 | for function in data['functions']:
212 | for offset in function['ranges']:
213 | startOffset = offset['startOffset']
214 | endOffset = offset['endOffset']
215 | positions.append((startOffset,endOffset))
216 | codepres_tmp=codepres_tmp+(self.make_codepre_tmp(index,url,positions))
217 | cov_profiler_temp = [jslists_tmp, codepres_tmp]
218 | return cov_profiler_temp
219 |
220 | def insert_backgroud(self, codedata, positions):
221 | """
222 | 将显示未覆盖代码的背景颜色的html标签,插入原始的js代码中,未覆盖代码会有黄色背景作为展示
223 | 【!重要的】这个方法采用了较为牺牲性能(比较懒)的插入方法,会对统计速度产生严重影响!
224 | :param codedata: 原始的js代码
225 | :param positions: 未被覆盖的js代码的索引
226 | :return: 插入背景色完毕的代码
227 | """
228 | sourcedata = copy.deepcopy(codedata)
229 | for index,position in enumerate(positions):
230 | codedata = list(codedata)
231 | count_code = "".join(sourcedata[position[0]:position[1]])
232 | if len(count_code) > 0 and count_code is not " ":
233 | insert_complet_str = """"""+count_code+""
234 | codedata = "".join(codedata)
235 | codedata = codedata.replace(count_code,insert_complet_str,1)
236 | codedata = "".join(codedata)
237 | codedata = self.code_html_filter(codedata)
238 | return codedata
239 |
240 | def make_profiler_report(self, covdata, stream):
241 | """
242 | 生成报告的方法
243 | :param covdata:覆盖率数据
244 | :param stream:写入流
245 | :return:
246 | """
247 | cov_profiler_temp = self.make_cov_profiler_temp(covdata)
248 | templet = self.MINTEMPLET % dict(
249 | jslist = cov_profiler_temp[0],
250 | pre = cov_profiler_temp[1]
251 | )
252 | stream.write(templet.encode('utf8'))
253 |
254 | def get_js(self, url):
255 | """
256 | 如果是本地文件则打开本地文件,不是则通过js的网络路径获取其代码
257 | :param url: js的网络路径
258 | :return: 获取到的js代码
259 | """
260 | if url.startswith('file:'):
261 | url_path = url[8:]
262 | with open(url_path, 'rb') as stream:
263 | js = stream.read()
264 | js = js.decode(encoding="utf-8")
265 | else:
266 | js = requests.get(url=url).text
267 | return js
268 |
269 | def make_covdata_file(self,cov_path,covdata,domains):
270 | """
271 | 接受原始的覆盖率数据,过滤并和本地数据对比(如果没有则生成),写入本地
272 | 【!你来你也这么多for】
273 | :param covdata:原始覆盖率数据
274 | :return:
275 | """
276 | covdata = self.merge_same_func_ranges(self.cov_not_count_filter(self.cov_domain_filter(covdata,domains)))
277 | try:
278 | with open(cov_path, 'rb') as cov_stream:
279 | covfile = pickle.load(cov_stream)
280 | covfile = self.merge_same_func_ranges(covfile)
281 | #如果新文件含有本地文件没有的js的统计情况,则在本地文件追加该js的统计情况
282 | #todo 这里jsid需要改为用jsname作为标识值
283 | covfile_jid = [data['scriptId'] for data in covfile]
284 | covdata_jid = [data['scriptId'] for data in covdata]
285 | difference_jid_list = [jid for jid in covdata_jid if jid not in covfile_jid]
286 | for item in difference_jid_list:
287 | for data in covdata:
288 | if data['scriptId'] == item:
289 | covfile.append(data)
290 | for item in covdata:
291 | scriptId = item['scriptId']
292 | for fileitem in covfile:
293 | if fileitem['scriptId'] == scriptId:
294 | #如果新文件的js统计中,有方法的统计情况和本地文件的该js的该方法的统计情况有出入,则按规则在本地文件内追加或者修改该统计情况
295 | for file_func in fileitem['functions']:
296 | funcname = file_func['functionName']
297 | data_func_ranges = [func['ranges'] for func in item['functions'] if func['functionName'] == funcname]
298 | data_func_ranges = [func_range for func_ranges in data_func_ranges for func_range in func_ranges]
299 | file_func_ranges = file_func['ranges']
300 | if data_func_ranges != file_func_ranges:
301 | ranges = file_func_ranges + [item for item in data_func_ranges if item not in file_func_ranges]
302 | ranges = self.by_rule_merge_ranges(ranges)
303 | file_func['ranges'] = ranges
304 | #如果新文件的js统计中,含有本地文件没有的该js的方法的统计情况,则在本地文件的该js统计中,追加该方法的统计情况
305 | difference_func_list = [func for func in item['functions'] if func not in fileitem['functions'] ]
306 | if difference_func_list:
307 | #print(difference_func_list)
308 | fileitem['functions'] = fileitem['functions']+difference_func_list
309 | with open(cov_path, 'wb') as cov_stream:
310 | pickle.dump(covfile, cov_stream)
311 | return covfile
312 | except FileNotFoundError:
313 | with open(cov_path, 'wb') as cov_stream:
314 | pickle.dump(covdata, cov_stream)
315 | return covdata
316 |
--------------------------------------------------------------------------------
/test.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 | Document
10 |
11 |
12 |
13 |
14 |
15 |
16 | 测试文本~~~
17 |
18 |
--------------------------------------------------------------------------------
/test.js:
--------------------------------------------------------------------------------
1 | function fa(){
2 | document.getElementById('H').innerHTML="点击了A,触发了A方法"
3 | };
4 | function fb(){
5 | document.getElementById('H').innerHTML="点击了B,触发了B方法"
6 | };
7 | function fc(){
8 | document.getElementById('H').innerHTML="点击了C,触发了C方法"
9 | }
10 |
--------------------------------------------------------------------------------
/test_other.js:
--------------------------------------------------------------------------------
1 | function fd(){
2 | document.getElementById('H').innerHTML="点击了D,触发了D方法,D在另一个js里"
3 | };
--------------------------------------------------------------------------------
/testcase.py:
--------------------------------------------------------------------------------
1 | import unittest
2 | import os
3 | import time
4 | import PyChromeDevTools
5 | import config
6 | from selenium import webdriver
7 | from interpret import Interpret
8 | from profiler import Profiler
9 |
10 | class TestCase(object):
11 | def __init__(self):
12 | #self.driver = webdriver
13 | self.interpret = Interpret()
14 | self.casename = ''
15 | self.actions = []
16 | self.testcases = []
17 | self.case_factroy()
18 |
19 | def init_context(self):
20 | self.casename = ''
21 | self.actions = []
22 |
23 | def case_factroy(self):
24 | for case in self.interpret.testcases:
25 | casename = case['casename']
26 | actions = case['actions']
27 | self.bind_actions(actions)
28 | self.bind_casename(casename)
29 | classname = casename.upper()
30 | testcase = type(classname, (unittest.TestCase,), {casename:run_action,
31 | 'actions':actions,
32 | #'driver':self.driver,
33 | 'tearDown':teardown,
34 | 'setUp':setup},)
35 | self.testcases.append(testcase(casename))
36 | self.init_context()
37 |
38 | def bind_actions(self, actions):
39 | self.actions=actions
40 |
41 | def bind_casename(self, casename):
42 | self.casename = casename
43 |
44 | def retry(times):
45 | def retry_func(func):
46 | def _(*args, **kwds):
47 | for i in range(times):
48 | try:
49 | func(*args, **kwds)
50 | return
51 | except AssertionError:
52 | pass
53 | raise AssertionError(func)
54 | return _
55 | return retry_func
56 |
57 | @retry(3)
58 | def run_action(self):
59 | for action in self.actions:
60 | for func, parmeters in action.items():
61 | func(*parmeters)
62 |
63 | def teardown(self):
64 | self.chrome.wait_event("Page.loadEventFired", timeout=60)
65 | time.sleep(3)
66 | cov = self.chrome.Profiler.takePreciseCoverage()
67 | self.chrome.Profiler.disable()
68 | #res = c['result']['result']
69 | cov = Profiler().make_covdata_file(config.cov_data_path,cov, ["zyj"])
70 | report_file = config.cov_report_path
71 | with open(report_file, 'wb') as report:
72 | Profiler().make_profiler_report(stream=report, covdata=cov)
73 | self.driver.close()
74 | self.chrome.close()
75 |
76 | def setup(self):
77 | #os.chdir(r"C:\Program Files (x86)\Google\Chrome\Application")
78 | os.chdir(config.chrome_install_path)
79 | cmd = "chrome.exe --remote-debugging-port=9222"
80 | os.popen(cmd)
81 | time.sleep(1)
82 | self.chrome = PyChromeDevTools.ChromeInterface()
83 | self.options = webdriver.ChromeOptions()
84 | self.options._debugger_address = "localhost:9222"
85 | self.driver = webdriver.Chrome(chrome_options=self.options)
86 | Interpret.replace_driver(self.actions, self.driver)
87 | Interpret.replace_chrome(self.actions, self.chrome)
88 | self.chrome.Profiler.enable()
89 | self.chrome.Profiler.startPreciseCoverage()
90 |
91 |
92 |
93 |
--------------------------------------------------------------------------------