\n\n def get_success_msg(self):\n \"\"\"获取成功信息\"\"\"\n e = self.wait_element_visible(self.success_msg_locator)\n> return e.text\nE AttributeError: 'NoneType' object has no attribute 'text'\n\npages\\main_page.py:71: AttributeError"}, "description": "退课失败", "attachments": [{"name": "log", "source": "8be4ce2d-e17b-4a2d-9172-6e42d2bd7b71-attachment.txt", "type": "text/plain"}], "parameters": [{"name": "test_info", "value": "{'password': 'zxz', 'expected': '密码错误'}"}], "start": 1595077597361, "stop": 1595077619243, "uuid": "42baa144-e0ef-4af1-909a-63d52683493b", "historyId": "0760b40c76161342aab7195da7ed70de", "testCaseId": "18a4f4731c8b19040ea89c2e2ccc77fb", "fullName": "tests.test_course.TestCourse#test_exit_error", "labels": [{"name": "tag", "value": "error"}, {"name": "tag", "value": "course"}, {"name": "parentSuite", "value": "tests"}, {"name": "suite", "value": "test_course"}, {"name": "subSuite", "value": "TestCourse"}, {"name": "host", "value": "DESKTOP-83PFO6C"}, {"name": "thread", "value": "12272-MainThread"}, {"name": "framework", "value": "pytest"}, {"name": "language", "value": "cpython3"}, {"name": "package", "value": "tests.test_course"}]}
--------------------------------------------------------------------------------
/tests/test_course.py:
--------------------------------------------------------------------------------
1 | import pytest
2 | from data.course_data import cases_success, join_cases_success, join_cases_error, exit_cases_error
3 | from pages.main_page import MainPage
4 |
5 |
6 | @pytest.mark.course
7 | class TestCourse:
8 | @pytest.mark.smoke
9 | @pytest.mark.success
10 | @pytest.mark.parametrize("test_info", cases_success)
11 | def test_join_exit_success(self, test_info, login_page):
12 | """加课、进入课堂、退课成功"""
13 | driver = login_page
14 | main_page = MainPage(driver)
15 | join_actual = main_page.get().input_class_code(test_info["course_code"]). \
16 | sure_join_course().get_success_msg()
17 | # class_page = main_page.enter_class()
18 | # code_actual = class_page.get_course_code()
19 | # main_page = class_page.click_class_btn()
20 | exit_actual = main_page.get().click_more_btn().exit_course(test_info["password"]).get_success_msg()
21 | try:
22 | assert join_actual == test_info["expected1"], "加课成功"
23 | # assert code_actual == test_info["expected2"], "进入课堂成功"
24 | assert exit_actual == test_info["expected3"], "退课成功"
25 | except AssertionError as e:
26 | raise e
27 |
28 | @pytest.mark.success
29 | @pytest.mark.parametrize("test_info", join_cases_success)
30 | def test_add_success(self, test_info, login_page):
31 | """加课成功"""
32 | driver = login_page
33 | main_page = MainPage(driver)
34 | actual = main_page.get().input_class_code(test_info["course_code"]). \
35 | sure_join_course().get_success_msg()
36 | try:
37 | assert actual == test_info["expected"], "加课成功"
38 | except AssertionError as e:
39 | raise e
40 |
41 | @pytest.mark.error
42 | @pytest.mark.parametrize("test_info", join_cases_error)
43 | def test_add_success(self, test_info, login_page):
44 | """加课失败"""
45 | driver = login_page
46 | main_page = MainPage(driver)
47 | actual = main_page.get().input_class_code(test_info["course_code"]). \
48 | sure_join_course().get_error_msg()
49 | try:
50 | assert actual == test_info["expected"], "加课失败"
51 | except AssertionError as e:
52 | raise e
53 |
54 | @pytest.mark.error
55 | @pytest.mark.parametrize("test_info", exit_cases_error)
56 | def test_exit_error(self, test_info, join_course):
57 | """退课失败"""
58 | driver = join_course
59 | main_page = MainPage(driver)
60 | actual = main_page.get().click_more_btn().exit_course(test_info["password"]).get_error_msg()
61 | try:
62 | assert actual == test_info["expected"], "退课失败"
63 | except AssertionError as e:
64 | raise e
65 |
--------------------------------------------------------------------------------
/课堂派项目介绍.md:
--------------------------------------------------------------------------------
1 | ## 课堂派web自动化项目
2 |
3 | 基于python+pytest+Selenium+allure,完成web自动化测试框架的搭建,并可以在jenkins上持续集成,应用于课堂派web自动化测试项目中,取得良好效果
4 |
5 | #### 一、自动化测试流程:
6 |
7 | 1. 需求分析
8 | 2. 挑选适合做自动化测试的功能
9 | 3. 设计测试用例
10 | 4. 搭建自动化测试环境
11 | 5. 设计自动化测试项目的架构
12 | 6. 编写代码
13 | 7. 执行测试用例
14 | 8. 生成测试报告并分析结果
15 |
16 | #### 二、项目介绍
17 |
18 | 课堂派是一款高效在线课堂管理平台
19 |
20 | **已知环境:**
21 |
22 | - 课堂派网址:https://www.ketangpai.com/ha
23 |
24 | - 加入考核班级邀请码:P69UVV
25 |
26 | **需要用自动化框架实现的功能**:
27 |
28 | 3.1 课堂:加入班级、进入班级、 退课
29 |
30 | 3.2 作业:上传作业、作业留言、查看作业提交状态
31 |
32 | 3.3 私信:发送私信
33 |
34 | #### 三、设计测试用例
35 |
36 | ##### 1.登录
37 |
38 | | 登录功能测试步骤 |
39 | | ------------------------------------------------------------ |
40 | | **前置条件**:联网 1.打开浏览器,初始化 (执行一次) |
41 | | (刷新页面,可重复执行多次) 2.输入页面地址或刷新 3.定位用户名 4.输入用户名 5.定位密码 6.输入密码 7.定位登录按钮 8.点击提交 9.定位用户的账号信息,断言(8.定位错误信息,断言) |
42 |
43 | ##### 2.课堂
44 |
45 | | 2.1 加入班级 | 2.2 进入班级 | 2.3 退出班级 |
46 | | ------------------------------------------------------------ | ----------------------------------------- | ------------------------------------------------------------ |
47 | | **前置条件**:已登录 | **前置条件**:已登录+已加入班级 | **前置条件**:已登录+已加入班级 1.登录 2.执行加入班级操作 3.判断,已加入则取消,未加入则继续 |
48 | | 1.定位加入课程 2.点击 3.定位加课验证码输入框 4.输入加课码 5.定位加入按钮 6.点击提交 7.定位加入成功信息,断言(定位错误信息,断言) | 1.定位班级 2.点击跳转至class界面 | 1.定位更多,点击 2.定位退课,点击 3.定位登录密码输入框 4.输入密码 5.定位退课按钮 6.点击提交 7.定位退课成功信息,断言(定位错误信息,断言) |
49 |
50 | ##### 3.作业
51 |
52 | 分析:有两种情况:
53 |
54 | - 第一次提交
55 | - 更新提交
56 |
57 | | 3.1 上传作业 | 3.2 作业留言 | 3.3 查看作业提交状态 |
58 | | ------------------------------------------------------------ | ------------------------------------------------------------ | ------------------------------------------------------------ |
59 | | **前置条件**:已登录+已加入班级+已进入作业页面 1.定位班级 2.点击跳转至class界面 3.定位作业,点击 4.定位作业标题 5.点击跳转至homework界面 | **前置条件**:已登录+已加入班级+已进入班级+已进入作业页面 1.定位班级 2.点击跳转至class界面 3.定位作业,点击 4.定位作业标题 5.点击跳转至homework界面 | **前置条件**:已登录+已加入班级+已进入作业页面 1.定位班级 2.点击跳转至class界面 3.定位作业,点击 4.定位作业标题 5.点击跳转至homework界面 |
60 | | 6.1定位添加作业文件按钮,点击 6.2定位更新提交按钮,点击 7.上传文件操作 8.定位提交按钮,点击提交 9.定位提交成功信息,断言(定位错误信息,断言) | 6.定位作业留言按钮,点击 7.定位留言输入框 8.清空输入框 9.输入留言 10.定位保存按钮,点击提交 11.定位作业留言按钮,获取留言信息,断言 | 6.定位作业留言按钮,点击 7.定位留言输入框 8.输入留言 9.定位保存按钮,点击提交 10.定位作业留言按钮,获取留言信息,断言 |
61 |
62 | ##### 4.私信
63 |
64 | | 私信功能测试步骤 |
65 | | ------------------------------------------------------------ |
66 | | **前置条件**:已登录+已加入班级+已进入班级+已进入students界面 1.定位班级 2.点击跳转至course界面 3.定位同学 4.点击跳转至student界面 |
67 | | 5.定位全部学生,点击 6.定位第一个学生,鼠标悬停 7.定位call图标,点击 8.定位会话输入框 9.输入消息 10.定位发送按钮 11.点击提交 12.定位发送的文本,断言 |
68 |
69 | #### 四、自动化测试环境
70 |
71 | python + selenium + pytest + chrom
72 |
73 | #### [Jenkins集成allure测试报告](https://www.cnblogs.com/linuxchao/p/linuxchao-jenkins-allure.html)
--------------------------------------------------------------------------------
/pages/homework_page.py:
--------------------------------------------------------------------------------
1 | from config import config
2 | from selenium.webdriver.common.by import By
3 | from common.base_page import BasePage
4 | import logging
5 | import time
6 |
7 |
8 | class HomeWorkPage(BasePage):
9 | """作业页面操作:
10 | 1. 添加作业文件
11 | 2. 提交
12 | 3. 作业留言:
13 | 6.定位作业留言按钮,点击
14 | 7.定位留言输入框 8.输入留言
15 | 9.定位保存按钮,点击提交
16 | 10.定位作业留言按钮,获取留言信息,断言
17 | 4. 查看提交日志
18 |
19 | """
20 | url = config.HOST + "/Homework/handup/homeworkid/MDAwMDAwMDAwMLScz5mHqa9s.html"
21 |
22 | # 更新提交
23 | submit_btn_locator = (By.XPATH, '//a[contains(@class, "tj-btn")]')
24 | new_submit_btn1_locator = (By.XPATH, '//a[@class="new-tj1"]')
25 | new_submit_btn2_locator = (By.XPATH, '//a[contains(@class, "new-tj2")]')
26 |
27 | sure_btn_locator = (By.XPATH, '//div[@class="btns"]//a[contains(@class, "active")]')
28 |
29 | add_files_btn_locator = (By.XPATH, '//input[@name="file"]')
30 |
31 | delete_file_btn_locator = (By.XPATH, '//a[text()="删除"]')
32 |
33 | success_message_locator = (By.XPATH, '//div[@class="weui_dialog_bd"]')
34 | known_locator = (By.XPATH, '//a[contains(@class, "weui_btn_dialog")]')
35 |
36 | message_btn_locator = (By.XPATH, '//span[@class="s2"]')
37 | comment_locator = (By.XPATH, '//textarea[@id="comment"]')
38 | save_btn_locator = (By.XPATH, '//textarea[@id="comment"]/following-sibling::a')
39 |
40 | view_status_locator = (By.XPATH, '//a[@class="togglesee"]')
41 | log_locator = (By.XPATH, '//div[@class="content"]//li//p')
42 |
43 | def get(self):
44 | """刷新页面"""
45 | self.driver.get(self.url)
46 | return self
47 |
48 | def upload_files(self, file_path):
49 | """上传作业"""
50 | try:
51 | e1 = self.find(self.new_submit_btn1_locator)
52 | e1.click()
53 | e2 = self.find(self.sure_btn_locator)
54 | e2.click()
55 | except Exception as err:
56 | logging.error(f"点击更新提交失败:{err}")
57 | e3 = self.find(self.add_files_btn_locator)
58 | e3.send_keys(file_path)
59 | return self
60 |
61 | def delete_file(self):
62 | """删除文件"""
63 | e = self.find(self.delete_file_btn_locator)
64 | e.click()
65 | return self
66 |
67 | def click_submit_files(self):
68 | """点击作业提交"""
69 | time.sleep(2)
70 | try:
71 | self.click_element(self.new_submit_btn2_locator)
72 | except Exception as err:
73 | logging.error(f"点击更新提交失败:{err}")
74 | self.click_element(self.submit_btn_locator)
75 | return self
76 |
77 | def get_success_message(self):
78 | """获取作业成功提交信息"""
79 | e = self.find(self.success_message_locator)
80 | return e.text
81 |
82 | def click_known(self):
83 | """点击知道了"""
84 | e = self.find(self.known_locator)
85 | e.click()
86 | return self
87 |
88 | def comment(self, message):
89 | """作业留言"""
90 | message_elem = self.find(self.message_btn_locator)
91 | message_elem.click()
92 | input_elem = self.clear_input_box(self.comment_locator)
93 | input_elem.send_keys(message)
94 | self.click_element(self.save_btn_locator)
95 | return self
96 |
97 | def get_comment(self):
98 | """获取留言信息"""
99 | e = self.find(self.message_btn_locator)
100 | return e.text
101 |
102 | def view_status(self):
103 | """查看提交日志"""
104 | e = self.find(self.view_status_locator)
105 | e.click()
106 | log_elem = self.find(self.log_locator)
107 | return log_elem.text
108 |
--------------------------------------------------------------------------------
/reports/assets/style.css:
--------------------------------------------------------------------------------
1 | body {
2 | font-family: Helvetica, Arial, sans-serif;
3 | font-size: 12px;
4 | /* do not increase min-width as some may use split screens */
5 | min-width: 800px;
6 | color: #999;
7 | }
8 |
9 | h1 {
10 | font-size: 24px;
11 | color: black;
12 | }
13 |
14 | h2 {
15 | font-size: 16px;
16 | color: black;
17 | }
18 |
19 | p {
20 | color: black;
21 | }
22 |
23 | a {
24 | color: #999;
25 | }
26 |
27 | table {
28 | border-collapse: collapse;
29 | }
30 |
31 | /******************************
32 | * SUMMARY INFORMATION
33 | ******************************/
34 |
35 | #environment td {
36 | padding: 5px;
37 | border: 1px solid #E6E6E6;
38 | }
39 |
40 | #environment tr:nth-child(odd) {
41 | background-color: #f6f6f6;
42 | }
43 |
44 | /******************************
45 | * TEST RESULT COLORS
46 | ******************************/
47 | span.passed, .passed .col-result {
48 | color: green;
49 | }
50 | span.skipped, span.xfailed, span.rerun, .skipped .col-result, .xfailed .col-result, .rerun .col-result {
51 | color: orange;
52 | }
53 | span.error, span.failed, span.xpassed, .error .col-result, .failed .col-result, .xpassed .col-result {
54 | color: red;
55 | }
56 |
57 |
58 | /******************************
59 | * RESULTS TABLE
60 | *
61 | * 1. Table Layout
62 | * 2. Extra
63 | * 3. Sorting items
64 | *
65 | ******************************/
66 |
67 | /*------------------
68 | * 1. Table Layout
69 | *------------------*/
70 |
71 | #results-table {
72 | border: 1px solid #e6e6e6;
73 | color: #999;
74 | font-size: 12px;
75 | width: 100%
76 | }
77 |
78 | #results-table th, #results-table td {
79 | padding: 5px;
80 | border: 1px solid #E6E6E6;
81 | text-align: left
82 | }
83 | #results-table th {
84 | font-weight: bold
85 | }
86 |
87 | /*------------------
88 | * 2. Extra
89 | *------------------*/
90 |
91 | .log:only-child {
92 | height: inherit
93 | }
94 | .log {
95 | background-color: #e6e6e6;
96 | border: 1px solid #e6e6e6;
97 | color: black;
98 | display: block;
99 | font-family: "Courier New", Courier, monospace;
100 | height: 230px;
101 | overflow-y: scroll;
102 | padding: 5px;
103 | white-space: pre-wrap
104 | }
105 | div.image {
106 | border: 1px solid #e6e6e6;
107 | float: right;
108 | height: 240px;
109 | margin-left: 5px;
110 | overflow: hidden;
111 | width: 320px
112 | }
113 | div.image img {
114 | width: 320px
115 | }
116 | div.video {
117 | border: 1px solid #e6e6e6;
118 | float: right;
119 | height: 240px;
120 | margin-left: 5px;
121 | overflow: hidden;
122 | width: 320px
123 | }
124 | div.video video {
125 | overflow: hidden;
126 | width: 320px;
127 | height: 240px;
128 | }
129 | .collapsed {
130 | display: none;
131 | }
132 | .expander::after {
133 | content: " (show details)";
134 | color: #BBB;
135 | font-style: italic;
136 | cursor: pointer;
137 | }
138 | .collapser::after {
139 | content: " (hide details)";
140 | color: #BBB;
141 | font-style: italic;
142 | cursor: pointer;
143 | }
144 |
145 | /*------------------
146 | * 3. Sorting items
147 | *------------------*/
148 | .sortable {
149 | cursor: pointer;
150 | }
151 |
152 | .sort-icon {
153 | font-size: 0px;
154 | float: left;
155 | margin-right: 5px;
156 | margin-top: 5px;
157 | /*triangle*/
158 | width: 0;
159 | height: 0;
160 | border-left: 8px solid transparent;
161 | border-right: 8px solid transparent;
162 | }
163 |
164 | .inactive .sort-icon {
165 | /*finish triangle*/
166 | border-top: 8px solid #E6E6E6;
167 | }
168 |
169 | .asc.active .sort-icon {
170 | /*finish triangle*/
171 | border-bottom: 8px solid #999;
172 | }
173 |
174 | .desc.active .sort-icon {
175 | /*finish triangle*/
176 | border-top: 8px solid #999;
177 | }
178 |
--------------------------------------------------------------------------------
/pages/main_page.py:
--------------------------------------------------------------------------------
1 | from config import config
2 | from selenium.webdriver.common.by import By
3 | from common.base_page import BasePage
4 | from pages.class_page import ClassPage
5 |
6 |
7 | class MainPage(BasePage):
8 | """主页页面操作:
9 | 1. 刷新首页
10 | 2. 输入加课码:定位加入课程,点击,定位加课验证码输入框,输入加课码
11 | 3. 定位确定按钮,点击确定
12 | 4. 定位取消按钮,点击取消
13 | 5. 获取错误信息
14 | 6. 获取成功信息
15 | 7. 退课操作:定位更多,点击,定位退课,点击,定位登录密码输入框,输入密码,定位退课按钮,点击提交
16 | 8. 进入班级操作:定位班级,点击跳转至class界面
17 | 9. 获取用户账户信息:定位
18 | """
19 | url = config.HOST + "/Main/index.html"
20 |
21 | user_account_locator = (By.XPATH, '//img[@class="avatar"]')
22 |
23 | add_class_btn_locator = (By.XPATH, '//div[contains(@class, "ktcon1l")]')
24 | input_add_class_code_locator = (By.XPATH, '//div[@class="chuangjiankccon"]//input[@type="text"]')
25 | sure_btn_locator = (By.XPATH, '//li[@class="cjli2"]//a[contains(@class, "btn-positive")]')
26 |
27 | error_msg_locator = (By.XPATH, '//div[@id="error-tip"]//span')
28 | success_msg_locator = (By.XPATH, '//div[@id="show-tip"]//span')
29 |
30 | more_btn_locator = (By.XPATH, '//a[contains(@class, "kdmore")]//span')
31 | exit_class_btn_01_locator = (By.XPATH, '//li[contains(@class, "kdli3")]//a')
32 | cancel_btn_locator = (By.XPATH, '//li[@class="cjli1"]//a')
33 | input_pwd_locator = (By.XPATH, '//input[@type="password"]')
34 | exit_class_btn_02_locator = (By.XPATH, '//li[@class="dli2"]//a[contains(@class, "btn-positive")]')
35 |
36 | enter_class_locator = (By.XPATH, '//a[@class="jumptoclass"]')
37 |
38 | course_code_locator = (By.XPATH, '//p[contains(@class,"invitecode")]')
39 |
40 | def get(self):
41 | """访问首页"""
42 | self.driver.get(self.url)
43 | return self
44 |
45 | def input_class_code(self, class_code):
46 | """输入课程码"""
47 | self.click_element(self.add_class_btn_locator)
48 | e = self.wait_element_visible(self.input_add_class_code_locator)
49 | e.send_keys(class_code)
50 | return self
51 |
52 | def sure_join_course(self):
53 | """点击确定加入"""
54 | self.click_element(self.sure_btn_locator)
55 | return self
56 |
57 | def cancel_class(self):
58 | """取消加入课程操作"""
59 | e = self.find(self.cancel_btn_locator)
60 | e.click()
61 | return self
62 |
63 | def get_error_msg(self):
64 | """获取错误信息"""
65 | e = self.wait_element_visible(self.error_msg_locator)
66 | return e.text
67 |
68 | def get_success_msg(self):
69 | """获取成功信息"""
70 | e = self.wait_element_visible(self.success_msg_locator)
71 | return e.text
72 |
73 | def click_more_btn(self):
74 | """点击更多按钮"""
75 | e = self.find(self.more_btn_locator)
76 | e.click()
77 | return self
78 |
79 | def exit_course(self, password):
80 | """退出课程"""
81 | # 点击退课按钮
82 | e1 = self.find(self.exit_class_btn_01_locator)
83 | e1.click()
84 | # 输入密码
85 | e = self.wait_element_visible(self.input_pwd_locator)
86 | e.send_keys(password)
87 | # 点击退课按钮
88 | self.click_element(self.exit_class_btn_02_locator)
89 | return self
90 |
91 | def enter_class(self):
92 | """进入班级操作"""
93 | e = self.find(self.enter_class_locator)
94 | e.click()
95 | return ClassPage(self.driver)
96 |
97 | def get_user_account(self):
98 | """获取用户的账号信息"""
99 | e = self.find(self.user_account_locator)
100 | return e.get_attribute("title")
101 |
102 | def get_course_code(self):
103 | """获取课程码"""
104 | e = self.find(self.course_code_locator)
105 | return e.text
106 |
--------------------------------------------------------------------------------
/common/base_page.py:
--------------------------------------------------------------------------------
1 | """
2 | base_page
3 | 页面对象有一些共同的基本操作,可以封装起来,并可以在基本操作当中,加上日志和截图的处理
4 | 1.查找元素,都需要等待
5 | 2.alert弹出框,窗口的切换
6 | 3.鼠标操作
7 | 4.上传文件
8 | """
9 | import os
10 | import logging
11 | from selenium.webdriver import Chrome
12 | from selenium.webdriver.support import expected_conditions
13 | from selenium.webdriver.support.wait import WebDriverWait
14 | from selenium.webdriver import ActionChains
15 | from config.config import IMAGE_PATH
16 | from datetime import datetime
17 | from pywinauto import Desktop
18 | import time
19 |
20 |
21 | class BasePage:
22 |
23 | def __init__(self, driver: Chrome): # 函数注解
24 | self.driver = driver
25 |
26 | def find(self, locator):
27 | """查找元素"""
28 | try:
29 | e = self.driver.find_element(*locator)
30 | except Exception as err:
31 | # 没有找到元素
32 | logging.error(f"元素定位失败:{err}")
33 | # 截图
34 | self.save_screenshot()
35 | else:
36 | return e
37 |
38 | def save_screenshot(self):
39 | """截图"""
40 | # 路径拼接
41 | # 时间戳命名,防止覆盖
42 | ts_str = datetime.now().strftime("%y-%m_%d-%H-%M-%S")
43 | file_name = os.path.join(IMAGE_PATH, ts_str) + '.png'
44 | self.driver.save_screenshot(file_name)
45 | return self
46 |
47 | def find_and_red(self, locator):
48 | """定位元素并标红,装饰器实现"""
49 | try:
50 | e = self.driver.find_element(*locator)
51 | js_code = "arguments[0].style.borderColor='red';"
52 | self.driver.execute_script(js_code, e)
53 | except Exception as err:
54 | logging.error(f"元素定位失败:{err}")
55 | self.save_screenshot()
56 | else:
57 | return e
58 |
59 | def wait_element_clickable(self, locator, timeout=20, poll_frequency=0.2):
60 | """等待元素可以被点击"""
61 | wait = WebDriverWait(self.driver, timeout, poll_frequency)
62 | try:
63 | e = wait.until(expected_conditions.element_to_be_clickable(locator))
64 | except Exception as err:
65 | logging.error(f"元素定位失败:{err}")
66 | self.save_screenshot()
67 | else:
68 | return e
69 |
70 | def wait_element_visible(self, locator, timeout=20, poll_frequency=0.2):
71 | """等待元素可见"""
72 | wait = WebDriverWait(self.driver, timeout, poll_frequency)
73 | try:
74 | e = wait.until(expected_conditions.visibility_of_element_located(locator))
75 | except Exception as err:
76 | logging.error(f"元素定位失败:{err}")
77 | self.save_screenshot()
78 | else:
79 | return e
80 |
81 | def wait_element_present(self, locator, timeout=20, poll_frequency=0.2):
82 | """等待元素被加载"""
83 | wait = WebDriverWait(self.driver, timeout, poll_frequency)
84 | try:
85 | e = wait.until(expected_conditions.presence_of_element_located(locator))
86 | except Exception as err:
87 | logging.error(f"元素定位失败:{err}")
88 | self.save_screenshot()
89 | else:
90 | return e
91 |
92 | def click_element(self, locator):
93 | """点击某个元素"""
94 | e = self.wait_element_clickable(locator)
95 | e.click()
96 | return self
97 |
98 | def double_click(self, locator):
99 | """双击某个元素"""
100 | e = self.wait_element_clickable(locator)
101 | ac = ActionChains(self.driver)
102 | ac.double_click(e).perform()
103 | return self
104 |
105 | def move_to(self, locator):
106 | """鼠标悬停"""
107 | e = self.find(locator)
108 | ac = ActionChains(self.driver)
109 | ac.move_to_element(e).perform()
110 | return self
111 |
112 | def switch_to_frame(self, e, wait=False):
113 | """iframe切换,内嵌网页"""
114 | if not wait:
115 | self.driver.switch_to.frame(e)
116 | return self
117 | wait = WebDriverWait(self.driver, 30)
118 | wait.until(expected_conditions.frame_to_be_available_and_switch_to_it(e))
119 | return self
120 |
121 | def add_file(self, file_path):
122 | """添加文件"""
123 | app = Desktop()
124 | dialog = app['打开']
125 | time.sleep(2)
126 | dialog["Edit"].type_keys(file_path)
127 | dialog["Button"].click()
128 |
129 | def clear_input_box(self, locator):
130 | input_box = self.find(locator)
131 | try:
132 | input_box.clear()
133 | except Exception as err:
134 | logging.error(f"输入框清空失败:{err}")
135 | finally:
136 | return input_box
137 |
138 |
139 | if __name__ == '__main__':
140 | pass
141 | # ts_str = datetime.now().strftime("%y-%m_%d-%H-%M-%S")
142 | # file_name = os.path.join(IMAGE_PATH, ts_str) + '.png'
143 | # print(file_name)
144 |
--------------------------------------------------------------------------------
/reports/test_message.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 | Test Report
6 |
7 |
8 |
245 | test_message.html
246 | Report generated on 18-Jul-2020 at 19:52:43 by pytest-html v2.1.1
247 | Environment
248 |
249 |
250 | JAVA_HOME
251 | D:\Java\jdk1.8.0_231
252 |
253 | Packages
254 | {"pluggy": "0.13.1", "py": "1.8.1", "pytest": "5.4.3"}
255 |
256 | Platform
257 | Windows-10-10.0.18362-SP0
258 |
259 | Plugins
260 | {"allure-pytest": "2.8.16", "html": "2.1.1", "metadata": "1.9.0", "rerunfailures": "9.0"}
261 |
262 | Python
263 | 3.7.3
264 | Summary
265 | 2 tests ran in 22.76 seconds.
266 | (Un)check the boxes to filter the results.
2 passed , 0 skipped , 0 failed , 0 errors , 0 expected failures , 0 unexpected passes , 0 rerun
267 | Results
268 |
269 |
270 |
271 | Result
272 | Test
273 | Duration
274 | Links
275 |
276 | No results found. Try to check the filters
277 |
278 |
279 | Passed
280 | tests/test_message.py::TestSendMessage::test_send_message_success[test_info0]
281 | 2.25
282 |
283 |
284 |
286 |
287 |
288 | Passed
289 | tests/test_message.py::TestSendMessage::test_send_message_success[test_info1]
290 | 1.97
291 |
292 |
293 |
--------------------------------------------------------------------------------
/reports/test_login.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 | Test Report
6 |
7 |
8 |
245 | test_smoe.html
246 | Report generated on 18-Jul-2020 at 19:47:14 by pytest-html v2.1.1
247 | Environment
248 |
249 |
250 | JAVA_HOME
251 | D:\Java\jdk1.8.0_231
252 |
253 | Packages
254 | {"pluggy": "0.13.1", "py": "1.8.1", "pytest": "5.4.3"}
255 |
256 | Platform
257 | Windows-10-10.0.18362-SP0
258 |
259 | Plugins
260 | {"allure-pytest": "2.8.16", "html": "2.1.1", "metadata": "1.9.0", "rerunfailures": "9.0"}
261 |
262 | Python
263 | 3.7.3
264 | Summary
265 | 5 tests ran in 18.14 seconds.
266 | (Un)check the boxes to filter the results.
5 passed , 0 skipped , 0 failed , 0 errors , 0 expected failures , 0 unexpected passes , 0 rerun
267 | Results
268 |
269 |
270 |
271 | Result
272 | Test
273 | Duration
274 | Links
275 |
276 | No results found. Try to check the filters
277 |
278 |
279 | Passed
280 | tests/test_login.py::TestLogin::test_login_success[test_info0]
281 | 3.64
282 |
283 |
284 |
286 |
287 |
288 | Passed
289 | tests/test_login.py::TestLogin::test_login_error[test_info0]
290 | 0.59
291 |
292 |
293 |
295 |
296 |
297 | Passed
298 | tests/test_login.py::TestLogin::test_login_error[test_info1]
299 | 0.52
300 |
301 |
302 |
304 |
305 |
306 | Passed
307 | tests/test_login.py::TestLogin::test_login_error[test_info2]
308 | 0.69
309 |
310 |
311 |
313 |
314 |
315 | Passed
316 | tests/test_login.py::TestLogin::test_login_error[test_info3]
317 | 0.73
318 |
319 |
320 |
--------------------------------------------------------------------------------
/reports/test_smoke.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 | Test Report
6 |
7 |
8 |
245 | test_smoke.html
246 | Report generated on 18-Jul-2020 at 20:41:40 by pytest-html v2.1.1
247 | Environment
248 |
249 |
250 | JAVA_HOME
251 | D:\Java\jdk1.8.0_231
252 |
253 | Packages
254 | {"pluggy": "0.13.1", "py": "1.8.1", "pytest": "5.4.3"}
255 |
256 | Platform
257 | Windows-10-10.0.18362-SP0
258 |
259 | Plugins
260 | {"allure-pytest": "2.8.16", "html": "2.1.1", "metadata": "1.9.0", "rerunfailures": "9.0"}
261 |
262 | Python
263 | 3.7.3
264 | Summary
265 | 6 tests ran in 95.92 seconds.
266 | (Un)check the boxes to filter the results.
6 passed , 0 skipped , 0 failed , 0 errors , 0 expected failures , 0 unexpected passes , 0 rerun
267 | Results
268 |
269 |
270 |
271 | Result
272 | Test
273 | Duration
274 | Links
275 |
276 | No results found. Try to check the filters
277 |
278 |
279 | Passed
280 | tests/test_course.py::TestCourse::test_join_exit_success[test_info0]
281 | 3.19
282 |
283 |
284 |
286 |
287 |
288 | Passed
289 | tests/test_homework.py::TestHomework::test_homework_success[test_info0]
290 | 4.44
291 |
292 |
293 |
295 |
296 |
297 | Passed
298 | tests/test_homework.py::TestHomework::test_homework_success[test_info1]
299 | 4.31
300 |
301 |
302 |
304 |
305 |
306 | Passed
307 | tests/test_login.py::TestLogin::test_login_success[test_info0]
308 | 13.96
309 |
310 |
311 |
313 |
314 |
315 | Passed
316 | tests/test_message.py::TestSendMessage::test_send_message_success[test_info0]
317 | 2.24
318 |
319 |
320 |
322 |
323 |
324 | Passed
325 | tests/test_message.py::TestSendMessage::test_send_message_success[test_info1]
326 | 1.95
327 |
328 |
329 |
--------------------------------------------------------------------------------
/reports/test_homework.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 | Test Report
6 |
7 |
8 |
245 | test_homework.html
246 | Report generated on 18-Jul-2020 at 20:37:40 by pytest-html v2.1.1
247 | Environment
248 |
249 |
250 | JAVA_HOME
251 | D:\Java\jdk1.8.0_231
252 |
253 | Packages
254 | {"pluggy": "0.13.1", "py": "1.8.1", "pytest": "5.4.3"}
255 |
256 | Platform
257 | Windows-10-10.0.18362-SP0
258 |
259 | Plugins
260 | {"allure-pytest": "2.8.16", "html": "2.1.1", "metadata": "1.9.0", "rerunfailures": "9.0"}
261 |
262 | Python
263 | 3.7.3
264 | Summary
265 | 7 tests ran in 52.56 seconds.
266 | (Un)check the boxes to filter the results.
7 passed , 0 skipped , 0 failed , 0 errors , 0 expected failures , 0 unexpected passes , 0 rerun
267 | Results
268 |
269 |
270 |
271 | Result
272 | Test
273 | Duration
274 | Links
275 |
276 | No results found. Try to check the filters
277 |
278 |
279 | Passed
280 | tests/test_homework.py::TestHomework::test_homework_success[test_info0]
281 | 4.92
282 |
283 |
284 |
286 |
287 |
288 | Passed
289 | tests/test_homework.py::TestHomework::test_homework_success[test_info1]
290 | 4.59
291 |
292 |
293 |
295 |
296 |
297 | Passed
298 | tests/test_homework.py::TestHomework::test_upload_homework_success[test_info0]
299 | 3.95
300 |
301 |
302 |
304 |
305 |
306 | Passed
307 | tests/test_homework.py::TestHomework::test_upload_homework_success[test_info1]
308 | 4.26
309 |
310 |
311 |
313 |
314 |
315 | Passed
316 | tests/test_homework.py::TestHomework::test_upload_homework_success[test_info2]
317 | 4.45
318 |
319 |
320 |
322 |
323 |
324 | Passed
325 | tests/test_homework.py::TestHomework::test_upload_homework_success[test_info3]
326 | 4.35
327 |
328 |
329 |
331 |
332 |
333 | Passed
334 | tests/test_homework.py::TestHomework::test_upload_homework_success[test_info4]
335 | 4.57
336 |
337 |
338 |
--------------------------------------------------------------------------------
/reports/test_course.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 | Test Report
6 |
7 |
8 |
245 | test_course.html
246 | Report generated on 18-Jul-2020 at 20:10:04 by pytest-html v2.1.1
247 | Environment
248 |
249 |
250 | JAVA_HOME
251 | D:\Java\jdk1.8.0_231
252 |
253 | Packages
254 | {"pluggy": "0.13.1", "py": "1.8.1", "pytest": "5.4.3"}
255 |
256 | Platform
257 | Windows-10-10.0.18362-SP0
258 |
259 | Plugins
260 | {"allure-pytest": "2.8.16", "html": "2.1.1", "metadata": "1.9.0", "rerunfailures": "9.0"}
261 |
262 | Python
263 | 3.7.3
264 | Summary
265 | 4 tests ran in 43.66 seconds.
266 | (Un)check the boxes to filter the results.
3 passed , 0 skipped , 1 failed , 0 errors , 0 expected failures , 0 unexpected passes , 0 rerun
267 | Results
268 |
269 |
270 |
271 | Result
272 | Test
273 | Duration
274 | Links
275 |
276 | No results found. Try to check the filters
277 |
278 |
279 | Failed
280 | tests/test_course.py::TestCourse::test_exit_error[test_info0]
281 | 20.95
282 |
283 |
284 |
286 |
287 |
288 | Passed
289 | tests/test_course.py::TestCourse::test_join_exit_success[test_info0]
290 | 4.02
291 |
292 |
293 |
295 |
296 |
297 | Passed
298 | tests/test_course.py::TestCourse::test_add_success[test_info0]
299 | 0.94
300 |
301 |
302 |
304 |
305 |
306 | Passed
307 | tests/test_course.py::TestCourse::test_add_success[test_info1]
308 | 1.18
309 |
310 |
311 |
--------------------------------------------------------------------------------