beanMap = applicationContext.getBeansOfType(type);
74 |
75 | return beanMap.values();
76 | }
77 |
78 | @Override
79 | public void setApplicationContext(ApplicationContext applicationContext) throws BeansException {
80 | BeanFactory.applicationContext = applicationContext;
81 | }
82 | }
83 |
--------------------------------------------------------------------------------
/autotest-base/src/main/java/com/qianmi/autotest/base/common/Logoutable.java:
--------------------------------------------------------------------------------
1 | package com.qianmi.autotest.base.common;
2 |
3 | /**
4 | * 退出当前用户
5 | * Created by liuzhaoming on 16/9/27.
6 | */
7 | public interface Logoutable {
8 | /**
9 | * 退出当前用户
10 | */
11 | void logout();
12 | }
13 |
--------------------------------------------------------------------------------
/autotest-base/src/main/java/com/qianmi/autotest/base/common/Module.java:
--------------------------------------------------------------------------------
1 | package com.qianmi.autotest.base.common;
2 |
3 | import java.lang.annotation.ElementType;
4 | import java.lang.annotation.Retention;
5 | import java.lang.annotation.RetentionPolicy;
6 | import java.lang.annotation.Target;
7 |
8 | /**
9 | * 测试模块, 比如订单、购物车等,模块不划分输入输出数据
10 | * Created by liuzhaoming on 2016/12/6.
11 | */
12 | @Target({ElementType.TYPE, ElementType.METHOD})
13 | @Retention(RetentionPolicy.RUNTIME)
14 | public @interface Module {
15 | String value();
16 | }
17 |
--------------------------------------------------------------------------------
/autotest-base/src/main/java/com/qianmi/autotest/base/common/Scene.java:
--------------------------------------------------------------------------------
1 | package com.qianmi.autotest.base.common;
2 |
3 | import java.lang.annotation.ElementType;
4 | import java.lang.annotation.Retention;
5 | import java.lang.annotation.RetentionPolicy;
6 | import java.lang.annotation.Target;
7 |
8 | /**
9 | * 测试场景
10 | * 测试场景可以区分输入输出数据
11 | * Created by liuzhaoming on 2016/11/14.
12 | */
13 | @Target({ElementType.TYPE, ElementType.METHOD})
14 | @Retention(RetentionPolicy.RUNTIME)
15 | public @interface Scene {
16 | String value();
17 | }
18 |
--------------------------------------------------------------------------------
/autotest-base/src/main/java/com/qianmi/autotest/base/page/AppLoginPage.java:
--------------------------------------------------------------------------------
1 | package com.qianmi.autotest.base.page;
2 |
3 | /**
4 | * APP登录页面
5 | * Created by liuzhaoming on 16/9/26.
6 | */
7 | public interface AppLoginPage {
8 | /**
9 | * 登录并跳转到首页
10 | *
11 | * @param username 用户名
12 | * @param password 密码
13 | */
14 | void login(String username, String password);
15 | }
16 |
--------------------------------------------------------------------------------
/autotest-base/src/main/java/com/qianmi/autotest/base/page/PageObject.java:
--------------------------------------------------------------------------------
1 | package com.qianmi.autotest.base.page;
2 |
3 | import org.slf4j.Logger;
4 | import org.slf4j.LoggerFactory;
5 |
6 | /**
7 | * Page Object 对象
8 | * Created by liuzhaoming on 2016/12/5.
9 | */
10 | public interface PageObject {
11 | Logger LOGGER = LoggerFactory.getLogger(PageObject.class);
12 |
13 | /**
14 | * 线程阻塞,供页面渲染
15 | */
16 | default void sleep(int secTime) {
17 | if (secTime > 0) {
18 | try {
19 | Thread.sleep(secTime * 1000);
20 | } catch (InterruptedException e) {
21 | LOGGER.error("Page sleep fail", e);
22 | }
23 | }
24 | }
25 |
26 | /**
27 | * 线程阻塞,供页面渲染
28 | *
29 | * @param millTime 毫秒时间
30 | */
31 | default void sleepInMillTime(int millTime) {
32 | if (millTime > 0) {
33 | try {
34 | Thread.sleep(millTime);
35 | } catch (InterruptedException e) {
36 | LOGGER.error("Page sleep fail", e);
37 | }
38 | }
39 | }
40 | }
41 |
--------------------------------------------------------------------------------
/autotest-base/src/main/java/com/qianmi/autotest/base/testng/BaseTestRetryAnalyzer.java:
--------------------------------------------------------------------------------
1 | package com.qianmi.autotest.base.testng;
2 |
3 | import lombok.extern.slf4j.Slf4j;
4 | import org.apache.commons.lang3.math.NumberUtils;
5 | import org.testng.IRetryAnalyzer;
6 | import org.testng.ITestResult;
7 | import org.testng.Reporter;
8 |
9 | /**
10 | * 执行用例失败重试
11 | * Created by liuzhaoming on 16/10/10.
12 | */
13 | @Slf4j
14 | public abstract class BaseTestRetryAnalyzer implements IRetryAnalyzer {
15 | private int retryCount = 1;
16 | private static int maxRetryCount = NumberUtils.toInt(System.getProperty("maxRetryCount"), 1);
17 |
18 | /**
19 | * Returns true if the test method has to be retried, false otherwise.
20 | *
21 | * @param result The result of the test method that just ran.
22 | * @return true if the test method has to be retried, false otherwise.
23 | */
24 | @Override
25 | public boolean retry(ITestResult result) {
26 | restart(result);
27 | if (retryCount <= maxRetryCount) {
28 | log.info("Retry for {} on class {} for {} times", result.getName(), result.getTestClass().getName(),
29 | retryCount);
30 | Reporter.setCurrentTestResult(result);
31 | Reporter.log("RunCount=" + (retryCount + 1));
32 | retryCount++;
33 |
34 | return true;
35 | }
36 |
37 | return false;
38 | }
39 |
40 | /**
41 | * 重启APP
42 | */
43 | protected abstract void restart(ITestResult result);
44 | }
45 |
--------------------------------------------------------------------------------
/autotest-base/src/main/java/com/qianmi/autotest/base/testng/QmDingNotifier.java:
--------------------------------------------------------------------------------
1 | package com.qianmi.autotest.base.testng;
2 |
3 | import com.qianmi.autotest.base.common.BeanFactory;
4 | import lombok.extern.slf4j.Slf4j;
5 | import org.apache.commons.lang3.StringUtils;
6 | import org.springframework.web.client.RestTemplate;
7 | import org.testng.ISuite;
8 | import org.testng.ISuiteListener;
9 | import org.testng.ISuiteResult;
10 |
11 | import java.net.InetAddress;
12 | import java.util.HashMap;
13 | import java.util.Map;
14 |
15 | /**
16 | * 钉钉通知执行结果
17 | * Created by liuzhaoming on 16/10/13.
18 | */
19 | @Slf4j
20 | public class QmDingNotifier implements ISuiteListener {
21 |
22 | private String testOutputDirectory = System.getProperty("testOutputDirectory");
23 |
24 | private String deviceName = System.getProperty("deviceName");
25 |
26 | private String operatorId = System.getProperty("operatorId");
27 |
28 | private String ip;
29 |
30 | public QmDingNotifier() {
31 | ip = getLocalHostIP();
32 | }
33 |
34 | /**
35 | * This method is invoked before the SuiteRunner starts.
36 | *
37 | * @param suite suite
38 | */
39 | @Override
40 | public void onStart(ISuite suite) {
41 |
42 | }
43 |
44 | /**
45 | * This method is invoked after the SuiteRunner has run all
46 | * the test suites.
47 | *
48 | * @param suite suite
49 | */
50 | @Override
51 | public void onFinish(ISuite suite) {
52 | if (needSendMessage()) {
53 | int skipCount = 0;
54 | int failCount = 0;
55 | for (ISuiteResult suiteResult : suite.getResults().values()) {
56 | skipCount += suiteResult.getTestContext().getSkippedTests().size();
57 | failCount += suiteResult.getTestContext().getFailedTests().size();
58 | }
59 |
60 | StringBuilder message = new StringBuilder();
61 | if (skipCount > 0 || failCount > 0) {//部分用例执行失败
62 | message.append(deviceName);
63 | message.append("自动化测试失败, 测试报告: ");
64 | message.append(getReportUrl());
65 | } else {
66 | message.append(deviceName);
67 | message.append("自动化测试通过, 测试报告: ");
68 | message.append(getReportUrl());
69 | }
70 |
71 | sendDingMsg(message.toString());
72 | }
73 | }
74 |
75 | /**
76 | * 判断是否需要发送订单消息
77 | *
78 | * @return boolean
79 | */
80 | private boolean needSendMessage() {
81 | return StringUtils.isNotBlank(testOutputDirectory) && StringUtils.isNotBlank(deviceName) && StringUtils
82 | .isNotBlank(ip);
83 | }
84 |
85 | /**
86 | * 获取本地IP地址
87 | *
88 | * @return 本地IP地址
89 | */
90 | private String getLocalHostIP() {
91 | String ip;
92 | try {
93 | InetAddress addr = InetAddress.getLocalHost();
94 | ip = addr.getHostAddress();
95 | } catch (Exception ex) {
96 | ip = "";
97 | }
98 |
99 | return ip;
100 | }
101 |
102 | /**
103 | * 获取测试报告的URL
104 | *
105 | * @return 测试报告URL
106 | */
107 | private String getReportUrl() {
108 | String[] temp = testOutputDirectory.split("/");
109 | String path = temp[temp.length - 1];
110 | return String.format("http://%s:8080/%s/%s-test-report.html", ip, path, deviceName);
111 | }
112 |
113 | /**
114 | * 发送钉钉通知
115 | *
116 | * @param message 通知内容
117 | */
118 | @SuppressWarnings("unchecked")
119 | private void sendDingMsg(String message) {
120 | RestTemplate restTemplate = BeanFactory.getBean(RestTemplate.class);
121 | if (null == restTemplate) {
122 | log.error("Cannot find RestTemplate instance");
123 | return;
124 | }
125 |
126 | if (StringUtils.isBlank(operatorId)) {
127 | log.error("Cannot find staffId");
128 | return;
129 | }
130 |
131 | try {
132 | String url = "http://ding.com/dingtalk/sendnotice?staffno={staffno}&content={content}";
133 | restTemplate.getForObject(url, Map.class, new HashMap() {
134 | {
135 | put("staffno", operatorId);
136 | put("content", message);
137 | }
138 | });
139 |
140 | } catch (Exception e) {
141 | log.error("Fail to send ding message {}", message, e);
142 | }
143 | }
144 | }
145 |
--------------------------------------------------------------------------------
/autotest-base/src/main/java/com/qianmi/autotest/base/testng/TestRetryListener.java:
--------------------------------------------------------------------------------
1 | package com.qianmi.autotest.base.testng;
2 |
3 | import org.testng.IAnnotationTransformer;
4 | import org.testng.IRetryAnalyzer;
5 | import org.testng.annotations.ITestAnnotation;
6 |
7 | import java.lang.reflect.Constructor;
8 | import java.lang.reflect.Method;
9 |
10 | /**
11 | * 失败重试监听器
12 | * Created by liuzhaoming on 16/10/10.
13 | */
14 | public class TestRetryListener implements IAnnotationTransformer {
15 | private Class extends BaseTestRetryAnalyzer> analyzerClass;
16 |
17 | public TestRetryListener(Class extends BaseTestRetryAnalyzer> analyzerClass) {
18 | this.analyzerClass = analyzerClass;
19 | }
20 |
21 | /**
22 | * This method will be invoked by TestNG to give you a chance
23 | * to modify a TestNG annotation read from your test classes.
24 | * You can change the values you need by calling any of the
25 | * setters on the ITest interface.
26 | *
27 | * Note that only one of the three parameters testClass,
28 | * testConstructor and testMethod will be non-null.
29 | *
30 | * @param annotation The annotation that was read from your
31 | * test class.
32 | * @param testClass If the annotation was found on a class, this
33 | * parameter represents this class (null otherwise).
34 | * @param testConstructor If the annotation was found on a constructor,
35 | * this parameter represents this constructor (null otherwise).
36 | * @param testMethod If the annotation was found on a method,
37 | */
38 | @Override
39 | public void transform(ITestAnnotation annotation, Class testClass, Constructor testConstructor, Method testMethod) {
40 | IRetryAnalyzer retry = annotation.getRetryAnalyzer();
41 | if (retry == null) {
42 | annotation.setRetryAnalyzer(analyzerClass);
43 | }
44 | }
45 | }
46 |
--------------------------------------------------------------------------------
/autotest-demo/autotest-demo-app/pom.xml:
--------------------------------------------------------------------------------
1 |
2 |
5 |
6 | autotest-demo
7 | com.qianmi
8 | 2.0.0-SNAPSHOT
9 |
10 | 4.0.0
11 |
12 | autotest-demo-app
13 | ${project.artifactId}
14 | APP自动化测试Demo
15 |
16 |
17 |
18 | com.qianmi
19 | autotest-app
20 | ${project.version}
21 |
22 |
23 |
24 |
25 |
26 |
27 | org.springframework.boot
28 | spring-boot-maven-plugin
29 |
30 |
31 |
32 | repackage
33 |
34 |
35 |
36 |
37 | com.qianmi.autotest.app.AppTestApplication
38 |
39 |
40 |
41 |
42 |
--------------------------------------------------------------------------------
/autotest-demo/autotest-demo-app/src/main/java/com/qianmi/autotest/demo/app/jd/page/HomePage.java:
--------------------------------------------------------------------------------
1 | package com.qianmi.autotest.demo.app.jd.page;
2 |
3 | import io.appium.java_client.pagefactory.AndroidFindBy;
4 | import io.appium.java_client.pagefactory.iOSFindBy;
5 | import org.openqa.selenium.WebElement;
6 | import org.springframework.stereotype.Component;
7 |
8 | /**
9 | * 京东APP首页
10 | * Created by liuzhaoming on 2018/12/25.
11 | */
12 | @Component
13 | public class HomePage extends NavigatePage {
14 |
15 | @AndroidFindBy(id = "com.jingdong.app.mall:id/a5f")
16 | @iOSFindBy(accessibility = "JDMainPage_input_gray")
17 | private WebElement searchButton;
18 |
19 | /**
20 | * 去搜索页
21 | *
22 | * @return 搜索页
23 | */
24 | public SearchPage gotoSearchPage() {
25 | wait(searchButton).click();
26 | return gotoPage(SearchPage.class);
27 | }
28 | }
29 |
--------------------------------------------------------------------------------
/autotest-demo/autotest-demo-app/src/main/java/com/qianmi/autotest/demo/app/jd/page/NavigatePage.java:
--------------------------------------------------------------------------------
1 | package com.qianmi.autotest.demo.app.jd.page;
2 |
3 | import com.qianmi.autotest.app.page.AppBasePage;
4 | import io.appium.java_client.pagefactory.AndroidFindBy;
5 | import io.appium.java_client.pagefactory.iOSFindBy;
6 | import org.openqa.selenium.WebElement;
7 |
8 | /**
9 | * 带有下方导航条的页面
10 | * Created by liuzhaoming on 2018/12/26.
11 | */
12 | public class NavigatePage extends AppBasePage {
13 |
14 | @AndroidFindBy(accessibility = "首页")
15 | @iOSFindBy(accessibility = "首页")
16 | private WebElement homeButton;
17 |
18 | @AndroidFindBy(accessibility = "分类")
19 | @iOSFindBy(accessibility = "分类")
20 | private WebElement categoryButton;
21 |
22 | @AndroidFindBy(accessibility = "发现")
23 | @iOSFindBy(accessibility = "发现")
24 | private WebElement discoveryButton;
25 |
26 | @AndroidFindBy(accessibility = "购物车")
27 | @iOSFindBy(accessibility = "购物车")
28 | private WebElement shoppingCartButton;
29 |
30 | @AndroidFindBy(accessibility = "我的")
31 | @iOSFindBy(accessibility = "我的")
32 | private WebElement mineButton;
33 |
34 | /**
35 | * 前往购物车页面
36 | *
37 | * @return 购物车页面
38 | */
39 | public ShoppingCartPage gotoCartPage() {
40 | wait(shoppingCartButton).click();
41 | return gotoPage(ShoppingCartPage.class);
42 | }
43 |
44 | /**
45 | * 前往首页
46 | *
47 | * @return 首页
48 | */
49 | public HomePage gotoHomePage() {
50 | wait(homeButton).click();
51 | return gotoPage(HomePage.class);
52 | }
53 | }
54 |
--------------------------------------------------------------------------------
/autotest-demo/autotest-demo-app/src/main/java/com/qianmi/autotest/demo/app/jd/page/ProductPage.java:
--------------------------------------------------------------------------------
1 | package com.qianmi.autotest.demo.app.jd.page;
2 |
3 | import com.qianmi.autotest.app.page.AppBasePage;
4 | import io.appium.java_client.pagefactory.AndroidFindBy;
5 | import io.appium.java_client.pagefactory.iOSFindBy;
6 | import org.openqa.selenium.WebElement;
7 | import org.springframework.stereotype.Component;
8 |
9 | /**
10 | * 商品页
11 | * Created by liuzhaoming on 2018/12/26.
12 | */
13 | @Component
14 | public class ProductPage extends AppBasePage {
15 |
16 | @AndroidFindBy(id = "com.jd.lib.productdetail:id/pd_invite_friend")
17 | @iOSFindBy(accessibility = "加入购物车")
18 | private WebElement addCartButton;
19 |
20 | @AndroidFindBy(id = "com.jd.lib.productdetail:id/title_back")
21 | @iOSFindBy(accessibility = "返回")
22 | private WebElement backButton;
23 |
24 | @AndroidFindBy(id = "com.jd.lib.productdetail:id/detail_style_add_2_car")
25 | @iOSFindBy(accessibility = "确定")
26 | private WebElement okButton;
27 |
28 | /**
29 | * 将商品添加到购物车
30 | *
31 | * @return 当前页
32 | */
33 | public ProductPage addCart() {
34 | wait(addCartButton).click();
35 | if (isExist(okButton, autotestProperties.getElementLoadTimeInMills())) {
36 | okButton.click();
37 | }
38 | return this;
39 | }
40 |
41 | /**
42 | * 返回搜索列表页
43 | *
44 | * @return 搜索列表页
45 | */
46 | public SearchResultPage backToSearchResultPage() {
47 | wait(backButton).click();
48 | return gotoPage(SearchResultPage.class);
49 | }
50 | }
51 |
--------------------------------------------------------------------------------
/autotest-demo/autotest-demo-app/src/main/java/com/qianmi/autotest/demo/app/jd/page/SearchPage.java:
--------------------------------------------------------------------------------
1 | package com.qianmi.autotest.demo.app.jd.page;
2 |
3 | import com.qianmi.autotest.app.page.AppBasePage;
4 | import io.appium.java_client.pagefactory.AndroidFindBy;
5 | import io.appium.java_client.pagefactory.iOSFindBy;
6 | import org.openqa.selenium.Keys;
7 | import org.openqa.selenium.WebElement;
8 | import org.springframework.stereotype.Component;
9 |
10 | /**
11 | * 商品搜索内容输入页
12 | * Created by liuzhaoming on 2018/12/25.
13 | */
14 | @Component
15 | public class SearchPage extends AppBasePage {
16 |
17 | @AndroidFindBy(id = "com.jd.lib.search:id/search_text")
18 | @iOSFindBy(accessibility = "搜索框")
19 | private WebElement searchField;
20 |
21 | @AndroidFindBy(id = "com.jingdong.app.mall:id/ax2")
22 | private WebElement searchButton;
23 |
24 | /**
25 | * 搜索商品
26 | *
27 | * @param keyword 关键词
28 | * @return 搜索结果页
29 | */
30 | public SearchResultPage search(String keyword) {
31 | wait(searchField).sendKeys(keyword);
32 | if (isAndroidPlatform()) {
33 | wait(searchButton).click();
34 | } else {
35 | wait(searchField).sendKeys(Keys.ENTER);
36 | }
37 | return gotoPage(SearchResultPage.class);
38 | }
39 | }
40 |
--------------------------------------------------------------------------------
/autotest-demo/autotest-demo-app/src/main/java/com/qianmi/autotest/demo/app/jd/page/SearchResultPage.java:
--------------------------------------------------------------------------------
1 | package com.qianmi.autotest.demo.app.jd.page;
2 |
3 | import com.qianmi.autotest.app.page.AppBasePage;
4 | import io.appium.java_client.pagefactory.AndroidFindBy;
5 | import io.appium.java_client.pagefactory.iOSFindBy;
6 | import org.openqa.selenium.WebElement;
7 | import org.springframework.stereotype.Component;
8 |
9 | /**
10 | * 商品搜索结果列表页
11 | * Created by liuzhaoming on 2018/12/25.
12 | */
13 | @Component
14 | public class SearchResultPage extends AppBasePage {
15 |
16 | @AndroidFindBy(id = "com.jd.lib.search:id/product_list_item")
17 | @iOSFindBy(accessibility = "com.jd.lib.search:id/product_list_item")
18 | private WebElement productItem;
19 |
20 | @AndroidFindBy(accessibility = "返回")
21 | @iOSFindBy(accessibility = "返回")
22 | private WebElement backButton;
23 |
24 | /**
25 | * 浏览列表首个商品详情
26 | *
27 | * @return 商品详情页
28 | */
29 | public ProductPage viewFirstProduct() {
30 | wait(productItem).click();
31 | return gotoPage(ProductPage.class);
32 | }
33 |
34 | /**
35 | * 返回APP首页
36 | *
37 | * @return APP首页
38 | */
39 | public HomePage backToHomePage() {
40 | wait(backButton).click();
41 | return gotoPage(HomePage.class);
42 | }
43 | }
44 |
--------------------------------------------------------------------------------
/autotest-demo/autotest-demo-app/src/main/java/com/qianmi/autotest/demo/app/jd/page/ShoppingCartPage.java:
--------------------------------------------------------------------------------
1 | package com.qianmi.autotest.demo.app.jd.page;
2 |
3 | import org.springframework.stereotype.Component;
4 |
5 | /**
6 | * 购物车页面
7 | * Created by liuzhaoming on 2018/12/26.
8 | */
9 | @Component
10 | public class ShoppingCartPage extends NavigatePage {
11 | }
12 |
--------------------------------------------------------------------------------
/autotest-demo/autotest-demo-app/src/main/java/com/qianmi/autotest/demo/app/jd/test/JdAppTest.java:
--------------------------------------------------------------------------------
1 | package com.qianmi.autotest.demo.app.jd.test;
2 |
3 | import com.qianmi.autotest.app.common.AppPageTest;
4 | import com.qianmi.autotest.demo.app.jd.page.HomePage;
5 | import org.testng.annotations.Test;
6 |
7 | /**
8 | * 京东商城APP测试用例
9 | * Created by liuzhaoming on 2018/12/25.
10 | */
11 | public class JdAppTest extends AppPageTest {
12 | /**
13 | * 测试未登录情况下的商品搜索和浏览
14 | * 页面跳转流程:
15 | * 1. 进入首页,点击搜索框
16 | * 2. 进入搜索页,输入关键词并输入确定
17 | * 3. 搜索结果列表找到第一条商品并查看商品详情
18 | * 4. 点击返回按钮回到搜索结果页
19 | * 5. 点击首页回到首页
20 | */
21 | @Test(priority = 1)
22 | public void testSearchWhenLogout() {
23 | String mate9Name = inputData.getProperty("searchProductName");
24 |
25 | pageFacade.gotoPage(HomePage.class)
26 | .gotoSearchPage()
27 | .search(mate9Name)
28 | .viewFirstProduct()
29 | .backToSearchResultPage()
30 | .backToHomePage();
31 | }
32 |
33 | /**
34 | * 测试未登录将商品添加到购物车
35 | * * 页面跳转流程:
36 | * 1. 进入首页,点击搜索框
37 | * 2. 进入搜索页,输入关键词并输入确定
38 | * 3. 搜索结果列表找到第一条商品并添加到购物车
39 | * 4. 进入购物车页面
40 | */
41 | @Test(priority = 2)
42 | public void testAddCart() {
43 | String mate9Name = inputData.getProperty("searchProductName");
44 |
45 | pageFacade.gotoPage(HomePage.class)
46 | .gotoSearchPage()
47 | .search(mate9Name)
48 | .viewFirstProduct()
49 | .addCart()
50 | .backToSearchResultPage()
51 | .backToHomePage()
52 | .gotoCartPage();
53 | }
54 | }
55 |
--------------------------------------------------------------------------------
/autotest-demo/autotest-demo-app/src/main/resources/application.properties:
--------------------------------------------------------------------------------
1 | #\u65E5\u5FD7\u7EA7\u522B\uFF0C\u5982\u679C\u662F\u5F00\u53D1\u9636\u6BB5\uFF0C\u53EF\u4EE5\u5C06\u65E5\u5FD7\u7EA7\u522B\u6539\u4E3Adebug\uFF0C\u65B9\u4FBF\u5B9A\u4F4D\u95EE\u9898
2 | logging.level.org.springframework=info
3 | logging.level.com.qianmi.autotest=info
4 | logging.level.org.reflections=info
5 | # \u6D4B\u8BD5\u7A0B\u5E8F\u9700\u8981Spring\u626B\u63CF\u7684\u5305\u8DEF\u5F84
6 | autotest.scanPackage=
7 | #\u9875\u9762\u76F8\u5173\u65F6\u95F4\u76F8\u5173\u914D\u7F6E
8 | # wait\u5143\u7D20\u7684\u603B\u65F6\u95F4,\u5355\u4F4D\u4E3A\u6BEB\u79D2,\u8D85\u8FC7\u6B64\u65F6\u95F4\u5C31\u8BA4\u4E3A\u8D85\u65F6
9 | autotest.elementLoadTimeInMills=10000
10 | # wait\u5143\u7D20\u7684\u8F6E\u8BE2\u95F4\u9694,\u5355\u4F4D\u4E3A\u6BEB\u79D2,\u5373wait\u67D0\u4E2A\u5143\u7D20\u65F6,\u6BCF\u9694\u591A\u5C11\u65F6\u95F4\u53D6\u67E5\u770B\u4E00\u6B21\u754C\u9762\u662F\u5426\u5B58\u5728\u6B64\u5143\u7D20
11 | autotest.refreshIntervalInMills=100
12 | # \u65B0\u9875\u9762\u52A0\u8F7D\u540E\u7B49\u5F85\u65F6\u95F4,\u5355\u4F4D\u4E3A\u6BEB\u79D2
13 | autotest.pageLoadTimeInMills=5000
14 | # \u9875\u9762\u5DE6\u53F3\u6ED1\u52A8\u65F6\u95F4,\u5355\u4F4D\u4E3A\u6BEB\u79D2
15 | autotest.swipeTimeInMills=1000
16 | #appium server\u76F8\u5173\u914D\u7F6E
17 | #\u662F\u5426\u8FDE\u63A5Appium server Manager
18 | autotest.appiumServerMngEnable=false
19 | #appium server\u5730\u5740,\u53EA\u6709\u5728\u4E0D\u8FDE\u63A5Appium server Manager\u60C5\u51B5\u4E0B\u751F\u6548
20 | autotest.appiumServerUrl=http://127.0.0.1:4723/wd/hub
21 | #Appium server Manager\u5730\u5740,\u53EF\u4EE5\u914D\u7F6E\u591A\u4E2A,\u4E2D\u95F4\u7528','\u5206\u9694
22 | autotest.appiumServerMngUrl=http://172.21.4.169:5000,http://172.21.4.170:5000,http://172.19.65.167:5000,http://172.19.65.174:5000
23 | #driver \u76F8\u5173\u914D\u7F6E
24 | #\u7A0B\u5E8FAPK\u8DEF\u5F84,\u53EA\u6709\u5728\u4E0D\u8FDE\u63A5Appium server Manager\u60C5\u51B5\u4E0B\u751F\u6548
25 | #autotest.driver.app=
26 | autotest.driver.newCommandTimeout=120
27 | ##\u5B89\u5353\u7684app\u5305\u540D
28 | autotest.driver.appPackage=com.jingdong.app.mall
29 | ## app\u4E3B\u7A0B\u5E8F\u754C\u9762\u7C7B
30 | autotest.driver.appActivity=com.jingdong.app.mall.main.MainActivity
31 | ## \u4F7F\u80FDunicode\u952E\u76D8
32 | autotest.driver.unicodeKeyboard=true
33 | ## \u91CD\u65B0\u8BBE\u7F6E\u952E\u76D8
34 | autotest.driver.resetKeyboard=true
35 | #\u7EC8\u7AEF\u914D\u7F6E
36 | #\u9ED8\u8BA4\u7EC8\u7AEF,\u53EA\u6709\u5728\u4E0D\u6307\u5B9A\u7EC8\u7AEF\u7684\u60C5\u51B5\u4E0B\u751F\u6548
37 | autotest.defaultDevice=oppor15
38 | #\u7EC8\u7AEFIP\u5730\u5740
39 | autotest.device.oppor15.ip=172.19.8.166
40 | #\u7EC8\u7AEF\u7248\u672C\u53F7
41 | autotest.device.oppor15.driver.platformVersion=8.1.0
42 | #\u7EC8\u7AEF\u5E73\u53F0\u540D\u79F0
43 | autotest.device.oppor15.driver.platformName=Android
44 | #\u7EC8\u7AEF\u8BBE\u5907\u540D\u79F0,\u76EE\u524D\u65E0\u7528
45 | autotest.device.oppor15.driver.deviceName=I7BAJ7IRSKSS9PU4
46 | autotest.device.oppor15.driver.automationName=UiAutomator2
47 | autotest.device.iphone6p.driver.platformVersion=12.1
48 | autotest.device.iphone6p.driver.platformName=iOS
49 | autotest.device.iphone6p.driver.deviceName=
50 | autotest.device.iphone6p.driver.udid=
51 | autotest.device.iphone6p.driver.automationName=XCUITest
52 | autotest.device.iphone6p.driver.bundleId=com.360buy.jdmobile
53 | autotest.device.iphone6p.driver.xcodeSigningId=iPhone Developer
54 | autotest.device.iphone6p.driver.xcodeOrgId=
--------------------------------------------------------------------------------
/autotest-demo/autotest-demo-app/src/main/resources/data.properties:
--------------------------------------------------------------------------------
1 | input.searchProductName=\u534E\u4E3A Mate20Pro
--------------------------------------------------------------------------------
/autotest-demo/autotest-demo-html5/pom.xml:
--------------------------------------------------------------------------------
1 |
2 |
5 |
6 | autotest-demo
7 | com.qianmi
8 | 2.0.0-SNAPSHOT
9 |
10 | 4.0.0
11 |
12 | autotest-demo-html5
13 |
14 |
15 |
16 | com.qianmi
17 | autotest-html5
18 | ${project.version}
19 |
20 |
21 |
22 |
23 |
24 |
25 | org.springframework.boot
26 | spring-boot-maven-plugin
27 |
28 |
29 |
30 | repackage
31 |
32 |
33 |
34 |
35 | com.qianmi.autotest.html5.Html5TestApplication
36 |
37 |
38 |
39 |
40 |
--------------------------------------------------------------------------------
/autotest-demo/autotest-demo-html5/src/main/java/com/qianmi/autotest/demo/html5/jd/page/HomePage.java:
--------------------------------------------------------------------------------
1 | package com.qianmi.autotest.demo.html5.jd.page;
2 |
3 | import org.openqa.selenium.WebElement;
4 | import org.openqa.selenium.support.FindBy;
5 | import org.springframework.stereotype.Component;
6 |
7 | /**
8 | * 京东H5首页
9 | * Created by liuzhaoming on 2019/1/21.
10 | */
11 | @Component
12 | public class HomePage extends NavigatePage {
13 | @FindBy(id = "msKeyWord")
14 | private WebElement searchButton;
15 |
16 | /**
17 | * 去搜索页
18 | *
19 | * @return 搜索页
20 | */
21 | public SearchPage gotoSearchPage() {
22 | wait(searchButton).click();
23 | return gotoPage(SearchPage.class);
24 | }
25 | }
26 |
--------------------------------------------------------------------------------
/autotest-demo/autotest-demo-html5/src/main/java/com/qianmi/autotest/demo/html5/jd/page/NavigatePage.java:
--------------------------------------------------------------------------------
1 | package com.qianmi.autotest.demo.html5.jd.page;
2 |
3 | import com.qianmi.autotest.html5.page.Html5Page;
4 | import org.openqa.selenium.WebElement;
5 | import org.openqa.selenium.support.FindBy;
6 |
7 | /**
8 | * 带有下方导航条的页面
9 | * Created by liuzhaoming on 2019/1/21.
10 | */
11 | public class NavigatePage extends Html5Page {
12 | @FindBy(id = "mCommonHome")
13 | private WebElement homePageButton;
14 |
15 | @FindBy(id = "分类")
16 | private WebElement categoryPageButton;
17 |
18 | @FindBy(id = "发现")
19 | private WebElement discoveryPageButton;
20 |
21 | @FindBy(id = "购物车")
22 | private WebElement cartPageButton;
23 |
24 | @FindBy(id = "我的")
25 | private WebElement minePageButton;
26 |
27 | /**
28 | * 前往购物车页面
29 | *
30 | * @return 购物车页面
31 | */
32 | public ShoppingCartPage gotoCartPage() {
33 | wait(minePageButton).click();
34 |
35 | return gotoPage(ShoppingCartPage.class);
36 | }
37 |
38 | /**
39 | * 前往首页
40 | *
41 | * @return 首页
42 | */
43 | public HomePage gotoHomePage() {
44 | wait(homePageButton).click();
45 |
46 | return gotoPage(HomePage.class);
47 | }
48 | }
49 |
--------------------------------------------------------------------------------
/autotest-demo/autotest-demo-html5/src/main/java/com/qianmi/autotest/demo/html5/jd/page/ProductPage.java:
--------------------------------------------------------------------------------
1 | package com.qianmi.autotest.demo.html5.jd.page;
2 |
3 | import com.qianmi.autotest.html5.page.Html5Page;
4 | import org.openqa.selenium.WebElement;
5 | import org.openqa.selenium.support.FindBy;
6 | import org.springframework.stereotype.Component;
7 |
8 | /**
9 | * 商品详情页
10 | * Created by liuzhaoming on 2019/1/21.
11 | */
12 | @Component
13 | public class ProductPage extends Html5Page {
14 | @FindBy(id = "addCart2")
15 | private WebElement addCartButton;
16 |
17 | @FindBy(id = "gotoCart")
18 | private WebElement cartButton;
19 |
20 | @FindBy(id = "m_common_header_goback")
21 | private WebElement backButton;
22 |
23 | @FindBy(id = "popupConfirm")
24 | private WebElement okButton;
25 |
26 | /**
27 | * 将商品添加到购物车
28 | *
29 | * @return 当前页
30 | */
31 | public ProductPage addCart() {
32 | wait(addCartButton).click();
33 | if (isExist(okButton, autotestProperties.getElementLoadTimeInMills())) {
34 | okButton.click();
35 | }
36 | return this;
37 | }
38 |
39 | /**
40 | * 跳转到购物车页面
41 | *
42 | * @return 购物车页面
43 | */
44 | public ShoppingCartPage gotoCartPage() {
45 | wait(cartButton).click();
46 | //无实际意义,主要是演示时让观众看得清楚,实际场景可删除
47 | sleep(3);
48 | return gotoPage(ShoppingCartPage.class);
49 | }
50 |
51 | /**
52 | * 返回搜索列表页
53 | *
54 | * @return 搜索列表页
55 | */
56 | public SearchResultPage backToSearchResultPage() {
57 | wait(backButton).click();
58 | return gotoPage(SearchResultPage.class);
59 | }
60 | }
61 |
--------------------------------------------------------------------------------
/autotest-demo/autotest-demo-html5/src/main/java/com/qianmi/autotest/demo/html5/jd/page/SearchPage.java:
--------------------------------------------------------------------------------
1 | package com.qianmi.autotest.demo.html5.jd.page;
2 |
3 | import com.qianmi.autotest.html5.page.Html5Page;
4 | import org.openqa.selenium.Keys;
5 | import org.openqa.selenium.WebElement;
6 | import org.openqa.selenium.support.FindBy;
7 | import org.springframework.stereotype.Component;
8 |
9 | /**
10 | * 搜索页
11 | * Created by liuzhaoming on 2019/1/21.
12 | */
13 | @Component
14 | public class SearchPage extends Html5Page {
15 | @FindBy(id = "msKeyWord")
16 | private WebElement searchField;
17 |
18 | /**
19 | * 搜索商品
20 | *
21 | * @param keyword 关键词
22 | * @return 搜索结果页
23 | */
24 | public SearchResultPage search(String keyword) {
25 | wait(searchField).sendKeys(keyword);
26 | wait(searchField).sendKeys(Keys.ENTER);
27 |
28 | return gotoPage(SearchResultPage.class);
29 | }
30 | }
31 |
--------------------------------------------------------------------------------
/autotest-demo/autotest-demo-html5/src/main/java/com/qianmi/autotest/demo/html5/jd/page/SearchResultPage.java:
--------------------------------------------------------------------------------
1 | package com.qianmi.autotest.demo.html5.jd.page;
2 |
3 | import com.qianmi.autotest.html5.page.Html5Page;
4 | import org.openqa.selenium.WebElement;
5 | import org.openqa.selenium.support.FindBy;
6 | import org.springframework.stereotype.Component;
7 |
8 | /**
9 | * 搜索结果页面
10 | * Created by liuzhaoming on 2019/1/21.
11 | */
12 | @Component
13 | public class SearchResultPage extends Html5Page {
14 | @FindBy(id = "itemList")
15 | private WebElement firstProductItem;
16 |
17 | @FindBy(id = "msCancelBtn")
18 | private WebElement backButton;
19 |
20 | /**
21 | * 浏览列表首个商品详情
22 | *
23 | * @return 商品详情页
24 | */
25 | public ProductPage viewFirstProduct() {
26 | wait(firstProductItem).click();
27 | return gotoPage(ProductPage.class);
28 | }
29 | }
30 |
--------------------------------------------------------------------------------
/autotest-demo/autotest-demo-html5/src/main/java/com/qianmi/autotest/demo/html5/jd/page/ShoppingCartPage.java:
--------------------------------------------------------------------------------
1 | package com.qianmi.autotest.demo.html5.jd.page;
2 |
3 | import org.springframework.stereotype.Component;
4 |
5 | /**
6 | * 购物车页面
7 | * Created by liuzhaoming on 2019/1/21.
8 | */
9 | @Component
10 | public class ShoppingCartPage extends NavigatePage {
11 | }
12 |
--------------------------------------------------------------------------------
/autotest-demo/autotest-demo-html5/src/main/java/com/qianmi/autotest/demo/html5/jd/test/JdHtml5Test.java:
--------------------------------------------------------------------------------
1 | package com.qianmi.autotest.demo.html5.jd.test;
2 |
3 | import com.qianmi.autotest.demo.html5.jd.page.HomePage;
4 | import com.qianmi.autotest.html5.common.Html5PageTest;
5 | import org.testng.annotations.Test;
6 |
7 | /**
8 | * 京东商城Html5测试用例
9 | * Created by liuzhaoming on 2018/12/25.
10 | */
11 | public class JdHtml5Test extends Html5PageTest {
12 | /**
13 | * 测试未登录情况下的商品搜索和浏览
14 | * 页面跳转流程:
15 | * 1. 进入首页,点击搜索框
16 | * 2. 进入搜索页,输入关键词并输入确定
17 | * 3. 搜索结果列表找到第一条商品并查看商品详情
18 | * 4. 点击返回按钮回到搜索结果页
19 | * 5. 点击首页回到首页
20 | */
21 | @Test(priority = 1)
22 | public void testSearchWhenLogout() {
23 | String mate9Name = inputData.getProperty("searchProductName");
24 |
25 | pageFacade.gotoPage(HomePage.class)
26 | .gotoSearchPage()
27 | .search(mate9Name)
28 | .viewFirstProduct()
29 | .backToSearchResultPage();
30 | }
31 |
32 | /**
33 | * 测试未登录将商品添加到购物车
34 | * * 页面跳转流程:
35 | * 1. 进入首页,点击搜索框
36 | * 2. 进入搜索页,输入关键词并输入确定
37 | * 3. 搜索结果列表找到第一条商品并添加到购物车
38 | * 4. 进入购物车页面
39 | */
40 | @Test(priority = 2)
41 | public void testAddCart() {
42 | String mate9Name = inputData.getProperty("searchProductName");
43 |
44 | pageFacade.gotoPage(HomePage.class)
45 | .gotoSearchPage()
46 | .search(mate9Name)
47 | .viewFirstProduct()
48 | .addCart()
49 | .gotoCartPage();
50 | }
51 | }
52 |
--------------------------------------------------------------------------------
/autotest-demo/autotest-demo-html5/src/main/resources/application.properties:
--------------------------------------------------------------------------------
1 | #\u65E5\u5FD7\u7EA7\u522B\uFF0C\u5982\u679C\u662F\u5F00\u53D1\u9636\u6BB5\uFF0C\u53EF\u4EE5\u5C06\u65E5\u5FD7\u7EA7\u522B\u6539\u4E3Adebug\uFF0C\u65B9\u4FBF\u5B9A\u4F4D\u95EE\u9898
2 | logging.level.org.springframework=info
3 | logging.level.com.qianmi.autotest=info
4 | logging.level.org.reflections=info
5 | # \u6D4B\u8BD5\u7A0B\u5E8F\u9700\u8981Spring\u626B\u63CF\u7684\u5305\u8DEF\u5F84
6 | autotest.scanPackage=
7 | #\u9875\u9762\u76F8\u5173\u65F6\u95F4\u76F8\u5173\u914D\u7F6E
8 | # wait\u5143\u7D20\u7684\u603B\u65F6\u95F4,\u5355\u4F4D\u4E3A\u6BEB\u79D2,\u8D85\u8FC7\u6B64\u65F6\u95F4\u5C31\u8BA4\u4E3A\u8D85\u65F6
9 | autotest.elementLoadTimeInMills=10000
10 | # wait\u5143\u7D20\u7684\u8F6E\u8BE2\u95F4\u9694,\u5355\u4F4D\u4E3A\u6BEB\u79D2,\u5373wait\u67D0\u4E2A\u5143\u7D20\u65F6,\u6BCF\u9694\u591A\u5C11\u65F6\u95F4\u53D6\u67E5\u770B\u4E00\u6B21\u754C\u9762\u662F\u5426\u5B58\u5728\u6B64\u5143\u7D20
11 | autotest.refreshIntervalInMills=100
12 | # \u65B0\u9875\u9762\u52A0\u8F7D\u540E\u7B49\u5F85\u65F6\u95F4,\u5355\u4F4D\u4E3A\u6BEB\u79D2
13 | autotest.pageLoadTimeInMills=5000
14 | # \u9875\u9762\u5DE6\u53F3\u6ED1\u52A8\u65F6\u95F4,\u5355\u4F4D\u4E3A\u6BEB\u79D2
15 | autotest.swipeTimeInMills=1000
16 | #appium server\u76F8\u5173\u914D\u7F6E
17 | #\u662F\u5426\u8FDE\u63A5Appium server Manager
18 | autotest.appiumServerMngEnable=false
19 | #appium server\u5730\u5740,\u53EA\u6709\u5728\u4E0D\u8FDE\u63A5Appium server Manager\u60C5\u51B5\u4E0B\u751F\u6548
20 | autotest.appiumServerUrl=http://127.0.0.1:4723/wd/hub
21 | #Appium server Manager\u5730\u5740,\u53EF\u4EE5\u914D\u7F6E\u591A\u4E2A,\u4E2D\u95F4\u7528','\u5206\u9694
22 | autotest.appiumServerMngUrl=http://172.21.4.169:5000,http://172.21.4.170:5000,http://172.19.65.167:5000,http://172.19.65.174:5000
23 | #driver \u76F8\u5173\u914D\u7F6E
24 | #\u7A0B\u5E8FAPK\u8DEF\u5F84,\u53EA\u6709\u5728\u4E0D\u8FDE\u63A5Appium server Manager\u60C5\u51B5\u4E0B\u751F\u6548
25 | #autotest.driver.app=
26 | autotest.driver.newCommandTimeout=120
27 | ## \u4F7F\u80FDunicode\u952E\u76D8
28 | autotest.driver.unicodeKeyboard=true
29 | ## \u91CD\u65B0\u8BBE\u7F6E\u952E\u76D8
30 | autotest.driver.resetKeyboard=true
31 | #\u7EC8\u7AEF\u914D\u7F6E
32 | #\u9ED8\u8BA4\u7EC8\u7AEF,\u53EA\u6709\u5728\u4E0D\u6307\u5B9A\u7EC8\u7AEF\u7684\u60C5\u51B5\u4E0B\u751F\u6548
33 | autotest.defaultDevice=oppor15
34 | #\u7EC8\u7AEFIP\u5730\u5740
35 | autotest.device.oppor15.ip=172.19.8.166
36 | #\u7EC8\u7AEF\u7248\u672C\u53F7
37 | autotest.device.oppor15.driver.platformVersion=8.1.0
38 | #\u7EC8\u7AEF\u5E73\u53F0\u540D\u79F0
39 | autotest.device.oppor15.driver.platformName=Android
40 | autotest.device.htcM8.driver.browserName=Chrome
41 | #\u7EC8\u7AEF\u8BBE\u5907\u540D\u79F0
42 | autotest.device.oppor15.driver.deviceName=I7BAJ7IRSKSS9PU4
43 | autotest.device.oppor15.driver.automationName=UiAutomator
44 | autotest.device.iphone6p.driver.platformVersion=12.1
45 | autotest.device.iphone6p.driver.platformName=iOS
46 | autotest.device.iphone6p.driver.deviceName=
47 | autotest.device.iphone6p.driver.udid=
48 | autotest.device.iphone6p.driver.automationName=XCUITest
49 | autotest.device.iphone6p.driver.bundleId=com.360buy.jdmobile
50 | autotest.device.iphone6p.driver.xcodeSigningId=iPhone Developer
51 | autotest.device.iphone6p.driver.xcodeOrgId=
52 | autotest.device.iphone6p.driver.browserName=Safari
53 | autotest.device.iphone6p.driver.safariAllowPopups=true
54 | autotest.device.iphone6p.driver.nativeWebTap=true
55 | autotest.device.iphone6p.driver.safariInitialUrl=http://m.jd.com/
--------------------------------------------------------------------------------
/autotest-demo/autotest-demo-html5/src/main/resources/data.properties:
--------------------------------------------------------------------------------
1 | input.startUrl=http://m.jd.com/
2 | input.searchProductName=\u534E\u4E3A Mate20Pro
--------------------------------------------------------------------------------
/autotest-demo/autotest-demo-web/pom.xml:
--------------------------------------------------------------------------------
1 |
2 |
5 |
6 | autotest-demo
7 | com.qianmi
8 | 2.0.0-SNAPSHOT
9 |
10 | 4.0.0
11 |
12 | autotest-demo-web
13 |
14 |
15 |
16 | com.qianmi
17 | autotest-web
18 | ${project.version}
19 |
20 |
21 |
22 |
23 |
24 |
25 | org.springframework.boot
26 | spring-boot-maven-plugin
27 |
28 |
29 |
30 | repackage
31 |
32 |
33 |
34 |
35 | com.qianmi.autotest.web.WebTestApplication
36 |
37 |
38 |
39 |
40 |
--------------------------------------------------------------------------------
/autotest-demo/autotest-demo-web/src/main/java/com/qianmi/autotest/demo/web/baidu/page/HomePage.java:
--------------------------------------------------------------------------------
1 | package com.qianmi.autotest.demo.web.baidu.page;
2 |
3 | import com.qianmi.autotest.web.page.WebBasePage;
4 | import org.openqa.selenium.WebElement;
5 | import org.openqa.selenium.support.FindBy;
6 | import org.springframework.stereotype.Component;
7 |
8 | /**
9 | * Baidu首页
10 | * Created by liuzhaoming on 2019/1/22.
11 | */
12 | @Component
13 | public class HomePage extends WebBasePage {
14 | @FindBy(id = "kw")
15 | private WebElement inputField;
16 |
17 | @FindBy(id = "su")
18 | private WebElement submitButton;
19 |
20 | public SearchResultPage search(String keyword) {
21 | wait(inputField).sendKeys(keyword);
22 | wait(submitButton).click();
23 |
24 | return gotoPage(SearchResultPage.class);
25 | }
26 | }
27 |
--------------------------------------------------------------------------------
/autotest-demo/autotest-demo-web/src/main/java/com/qianmi/autotest/demo/web/baidu/page/SearchResultPage.java:
--------------------------------------------------------------------------------
1 | package com.qianmi.autotest.demo.web.baidu.page;
2 |
3 | import com.qianmi.autotest.web.page.WebBasePage;
4 | import org.openqa.selenium.WebElement;
5 | import org.openqa.selenium.support.FindBy;
6 | import org.springframework.stereotype.Component;
7 |
8 | /**
9 | * Baidu搜索结果页面
10 | * Created by liuzhaoming on 2019/1/22.
11 | */
12 | @Component
13 | public class SearchResultPage extends WebBasePage {
14 | @FindBy(xpath = "//div[@id=\"content_left\"]/div[@id=\"1\"]")
15 | private WebElement firstItem;
16 |
17 | public boolean checkResult(String keyword) {
18 | return wait(firstItem).getText().contains(keyword);
19 | }
20 | }
21 |
--------------------------------------------------------------------------------
/autotest-demo/autotest-demo-web/src/main/java/com/qianmi/autotest/demo/web/baidu/test/BaiduWebTest.java:
--------------------------------------------------------------------------------
1 | package com.qianmi.autotest.demo.web.baidu.test;
2 |
3 | import com.qianmi.autotest.demo.web.baidu.page.HomePage;
4 | import com.qianmi.autotest.demo.web.baidu.page.SearchResultPage;
5 | import com.qianmi.autotest.web.common.WebPageTest;
6 | import org.testng.Assert;
7 | import org.testng.annotations.Test;
8 |
9 | /**
10 | * 百度Web页面测试
11 | * Created by liuzhaoming on 2019/1/22.
12 | */
13 | public class BaiduWebTest extends WebPageTest {
14 | @Test
15 | public void searchBaidu() {
16 | String keyword = "百度";
17 | SearchResultPage searchResultPage = pageFacade.gotoPage(HomePage.class).search(keyword);
18 | Assert.assertTrue(searchResultPage.checkResult(keyword));
19 | }
20 |
21 | @Test(priority = 1)
22 | public void searchYouzhan() {
23 | String keyword = "有赞";
24 | SearchResultPage searchResultPage = pageFacade.gotoPage(HomePage.class).search(keyword);
25 | Assert.assertTrue(searchResultPage.checkResult(keyword));
26 | }
27 | }
28 |
--------------------------------------------------------------------------------
/autotest-demo/autotest-demo-web/src/main/resources/application.properties:
--------------------------------------------------------------------------------
1 | #\u65E5\u5FD7\u7EA7\u522B\uFF0C\u5982\u679C\u662F\u5F00\u53D1\u9636\u6BB5\uFF0C\u53EF\u4EE5\u5C06\u65E5\u5FD7\u7EA7\u522B\u6539\u4E3Adebug\uFF0C\u65B9\u4FBF\u5B9A\u4F4D\u95EE\u9898
2 | logging.level.org.springframework=info
3 | logging.level.com.qianmi.autotest=info
4 | logging.level.org.reflections=info
5 | # \u6D4B\u8BD5\u7A0B\u5E8F\u9700\u8981Spring\u626B\u63CF\u7684\u5305\u8DEF\u5F84
6 | autotest.scanPackage=
7 | #\u9875\u9762\u76F8\u5173\u65F6\u95F4\u76F8\u5173\u914D\u7F6E
8 | # wait\u5143\u7D20\u7684\u603B\u65F6\u95F4,\u5355\u4F4D\u4E3A\u6BEB\u79D2,\u8D85\u8FC7\u6B64\u65F6\u95F4\u5C31\u8BA4\u4E3A\u8D85\u65F6
9 | autotest.elementLoadTimeInMills=10000
10 | # wait\u5143\u7D20\u7684\u8F6E\u8BE2\u95F4\u9694,\u5355\u4F4D\u4E3A\u6BEB\u79D2,\u5373wait\u67D0\u4E2A\u5143\u7D20\u65F6,\u6BCF\u9694\u591A\u5C11\u65F6\u95F4\u53D6\u67E5\u770B\u4E00\u6B21\u754C\u9762\u662F\u5426\u5B58\u5728\u6B64\u5143\u7D20
11 | autotest.refreshIntervalInMills=100
12 | # \u65B0\u9875\u9762\u52A0\u8F7D\u540E\u7B49\u5F85\u65F6\u95F4,\u5355\u4F4D\u4E3A\u6BEB\u79D2
13 | autotest.pageLoadTimeInMills=5000
14 | # \u9875\u9762\u5DE6\u53F3\u6ED1\u52A8\u65F6\u95F4,\u5355\u4F4D\u4E3A\u6BEB\u79D2
15 | autotest.swipeTimeInMills=1000
16 | #\u6D4F\u89C8\u5668\u914D\u7F6E
17 | #\u9ED8\u8BA4\u6D4F\u89C8\u5668,\u53EA\u6709\u5728\u4E0D\u6307\u5B9A\u6D4F\u89C8\u5668\u7684\u60C5\u51B5\u4E0B\u751F\u6548,\u76EE\u524D\u652F\u6301Chrome Firefox IE
18 | autotest.defaultBrowserName=Chrome
--------------------------------------------------------------------------------
/autotest-demo/autotest-demo-web/src/main/resources/data.properties:
--------------------------------------------------------------------------------
1 | input.startUrl=http://baidu.com/
2 | input.searchProductName=\u534E\u4E3A Mate20Pro
--------------------------------------------------------------------------------
/autotest-demo/pom.xml:
--------------------------------------------------------------------------------
1 |
2 |
5 |
6 | autotest-parent
7 | com.qianmi
8 | 2.0.0-SNAPSHOT
9 |
10 | 4.0.0
11 |
12 | autotest-demo
13 | pom
14 | ${project.artifactId}
15 | 自动化测试框架Demo
16 |
17 | autotest-demo-app
18 | autotest-demo-html5
19 | autotest-demo-web
20 |
21 |
22 |
23 |
--------------------------------------------------------------------------------
/autotest-html5/pom.xml:
--------------------------------------------------------------------------------
1 |
2 |
4 |
5 | autotest-parent
6 | com.qianmi
7 | 2.0.0-SNAPSHOT
8 | ../pom.xml
9 |
10 | 4.0.0
11 |
12 | autotest-html5
13 | 移动端 HTML5 UI 自动化测试框架
14 |
15 |
16 |
17 | com.qianmi
18 | autotest-appium
19 | ${project.version}
20 |
21 |
22 | org.projectlombok
23 | lombok
24 |
25 |
26 |
--------------------------------------------------------------------------------
/autotest-html5/src/main/java/com/qianmi/autotest/html5/Html5TestApplication.java:
--------------------------------------------------------------------------------
1 | package com.qianmi.autotest.html5;
2 |
3 | import com.qianmi.autotest.appium.testng.ScreenShotListener;
4 | import com.qianmi.autotest.base.AbstractTestApplication;
5 | import com.qianmi.autotest.base.testng.DefaultReporter;
6 | import com.qianmi.autotest.base.testng.QmDingNotifier;
7 | import com.qianmi.autotest.base.testng.TestRetryListener;
8 | import com.qianmi.autotest.html5.testng.Html5TestRetryAnalyzer;
9 | import org.springframework.boot.autoconfigure.SpringBootApplication;
10 | import org.testng.ITestNGListener;
11 |
12 | import java.util.ArrayList;
13 | import java.util.List;
14 |
15 | /**
16 | * H5测试主程序
17 | * Created by liuzhaoming on 2016/12/5.
18 | */
19 | @SpringBootApplication(scanBasePackages = {"com.qianmi.autotest", "${autotest.scanPackage:}"})
20 | public class Html5TestApplication extends AbstractTestApplication {
21 | public static void main(String[] args) {
22 | Html5TestApplication h5Application = new Html5TestApplication();
23 | h5Application.runTest(args);
24 | }
25 |
26 | /**
27 | * 子类可以重新此方法
28 | *
29 | * @return List 监听器
30 | */
31 | @Override
32 | protected List getListeners() {
33 | List listeners = new ArrayList<>();
34 | listeners.add(new DefaultReporter());
35 |
36 | if (Boolean.valueOf(System.getProperty("screenshot"))) {
37 | listeners.add(new ScreenShotListener());
38 | }
39 |
40 | if (Boolean.valueOf(System.getProperty("testRetry"))) {
41 | listeners.add(new TestRetryListener(Html5TestRetryAnalyzer.class));
42 | }
43 |
44 | if (Boolean.valueOf(System.getProperty("dingNotice"))) {
45 | listeners.add(new QmDingNotifier());
46 | }
47 | return listeners;
48 | }
49 | }
50 |
--------------------------------------------------------------------------------
/autotest-html5/src/main/java/com/qianmi/autotest/html5/common/Html5PageTest.java:
--------------------------------------------------------------------------------
1 | package com.qianmi.autotest.html5.common;
2 |
3 | import com.qianmi.autotest.appium.data.DataProvider;
4 | import com.qianmi.autotest.base.common.AutotestUtils;
5 | import com.qianmi.autotest.base.common.BasePageTest;
6 | import com.qianmi.autotest.base.common.BeanFactory;
7 | import com.qianmi.autotest.base.page.AppLoginPage;
8 | import com.qianmi.autotest.html5.Html5TestApplication;
9 | import com.qianmi.autotest.html5.page.Html5PageFacade;
10 | import io.appium.java_client.AppiumDriver;
11 | import io.appium.java_client.pagefactory.AppiumFieldDecorator;
12 | import lombok.extern.slf4j.Slf4j;
13 | import org.apache.commons.lang3.StringUtils;
14 | import org.openqa.selenium.support.PageFactory;
15 | import org.springframework.beans.factory.annotation.Autowired;
16 | import org.springframework.boot.test.context.SpringBootTest;
17 | import org.testng.annotations.BeforeMethod;
18 |
19 | import javax.annotation.Resource;
20 | import java.lang.reflect.Method;
21 | import java.time.Duration;
22 |
23 | /**
24 | * H5 测试类基类
25 | * Created by liuzhaoming on 2016/12/6.
26 | */
27 | @SuppressWarnings({"unused", "WeakerAccess"})
28 | @SpringBootTest(classes = Html5TestApplication.class)
29 | @Slf4j
30 | public class Html5PageTest extends BasePageTest {
31 |
32 | @Autowired
33 | protected Html5PageFacade pageFacade;
34 |
35 | @Autowired
36 | protected AppiumDriver appiumDriver;
37 |
38 | @Resource
39 | protected DataProvider inputData;
40 |
41 | @Resource
42 | protected DataProvider outputData;
43 |
44 | @BeforeMethod
45 | public void login(Method method) {
46 | AppLoginPage loginPage = BeanFactory.getBeanByType(AppLoginPage.class);
47 | String sceneName = AutotestUtils.getSceneName(method);
48 | String userName = inputData.getProperty("userName", sceneName);
49 | String password = inputData.getProperty("password", sceneName);
50 | String startUrl = inputData.getProperty("startUrl", sceneName);
51 |
52 | try {
53 | appiumDriver.get(startUrl);
54 | AutotestUtils.sleep(1000);
55 | if (null != loginPage && StringUtils.isNoneBlank(userName)) {
56 | PageFactory.initElements(new AppiumFieldDecorator(appiumDriver, Duration.ofSeconds(1)), loginPage);
57 | loginPage.login(userName, password);
58 | }
59 | } catch (Exception e) {
60 | log.warn("Login has error", e);
61 | AutotestUtils.sleep(1000);
62 | try {
63 | if (null != loginPage && StringUtils.isNoneBlank(userName)) {
64 | loginPage.login(userName, password);
65 | }
66 | } catch (Exception ignored) {
67 | }
68 | }
69 | }
70 | }
71 |
--------------------------------------------------------------------------------
/autotest-html5/src/main/java/com/qianmi/autotest/html5/common/Html5ResourceLoader.java:
--------------------------------------------------------------------------------
1 | package com.qianmi.autotest.html5.common;
2 |
3 | import com.qianmi.autotest.appium.common.AppiumAutotestProperties;
4 | import com.qianmi.autotest.appium.common.AppiumResourceLoader;
5 | import com.qianmi.autotest.appium.data.DataProvider;
6 | import com.qianmi.autotest.base.common.AutotestException;
7 | import io.appium.java_client.AppiumDriver;
8 | import io.appium.java_client.android.AndroidDriver;
9 | import io.appium.java_client.ios.IOSDriver;
10 | import lombok.extern.slf4j.Slf4j;
11 | import org.apache.commons.lang3.StringUtils;
12 | import org.openqa.selenium.remote.DesiredCapabilities;
13 | import org.springframework.context.annotation.Bean;
14 | import org.springframework.stereotype.Component;
15 |
16 | import javax.annotation.Resource;
17 | import java.net.URL;
18 | import java.util.Properties;
19 |
20 | /**
21 | * 系统资源加载
22 | * Created by liuzhaoming on 2016/12/6.
23 | */
24 | @Component
25 | @Slf4j
26 | @SuppressWarnings("unused")
27 | public class Html5ResourceLoader extends AppiumResourceLoader {
28 |
29 | @Resource(name = "inputData")
30 | private DataProvider inputData;
31 |
32 | /**
33 | * 页面跳转
34 | *
35 | * @param url 要跳转的页面URL
36 | */
37 | public void gotoPage(String url) {
38 | try {
39 | AppiumDriver appiumDriver = getAppiumDriver();
40 |
41 | if (null == appiumDriver) {
42 | log.error("Goto page fail because appium server is null");
43 | return;
44 | }
45 |
46 | appiumDriver.get(url);
47 | } catch (Exception e) {
48 | log.error("Cannot gotoPage {}", url, e);
49 | }
50 | }
51 |
52 | @Bean
53 | public AppiumDriver appiumDriver(AppiumAutotestProperties appiumProperties) {
54 | String appiumServerUrl = initAppiumServer(appiumProperties);
55 | Properties driverConfig = appiumProperties.getDriverConfig();
56 | DesiredCapabilities capabilities = new DesiredCapabilities();
57 | for (String name : driverConfig.stringPropertyNames()) {
58 | setCapabilities(name, driverConfig.getProperty(name), capabilities);
59 | }
60 |
61 |
62 | try {
63 | AppiumDriver appiumDriver;
64 | String browserName = driverConfig.getProperty("browserName");
65 | if (driverConfig.getProperty("platformName", "Android").equalsIgnoreCase("ios")) {
66 | if (StringUtils.isBlank(browserName)) {
67 | setCapabilities("browserName", "Safari", capabilities);
68 | }
69 |
70 | appiumDriver = new IOSDriver(new URL(appiumServerUrl), capabilities);
71 | } else {
72 | if (StringUtils.isBlank(browserName)) {
73 | setCapabilities("browserName", "Chrome", capabilities);
74 | }
75 |
76 | appiumDriver = new AndroidDriver(new URL(appiumServerUrl), capabilities);
77 | String startUrl = inputData.getProperty("startUrl");
78 | if (StringUtils.isNotBlank(startUrl)) {
79 | appiumDriver.get(startUrl);
80 | }
81 | }
82 | return appiumDriver;
83 | } catch (Exception e) {
84 | log.error("Create appium driver fail {}", appiumServerUrl, e);
85 | throw new AutotestException("Create appium driver fail " + appiumServerUrl);
86 | }
87 | }
88 | }
89 |
--------------------------------------------------------------------------------
/autotest-html5/src/main/java/com/qianmi/autotest/html5/page/Html5Page.java:
--------------------------------------------------------------------------------
1 | package com.qianmi.autotest.html5.page;
2 |
3 | import com.qianmi.autotest.appium.page.AppiumBasePage;
4 | import com.qianmi.autotest.base.common.AutotestUtils;
5 | import io.appium.java_client.MobileElement;
6 | import io.appium.java_client.TouchAction;
7 | import io.appium.java_client.ios.IOSDriver;
8 | import io.appium.java_client.touch.offset.PointOption;
9 | import lombok.extern.slf4j.Slf4j;
10 | import org.apache.commons.lang3.StringUtils;
11 | import org.apache.commons.lang3.math.NumberUtils;
12 | import org.openqa.selenium.Dimension;
13 | import org.openqa.selenium.Keys;
14 | import org.openqa.selenium.NoSuchElementException;
15 | import org.openqa.selenium.WebElement;
16 | import org.openqa.selenium.support.FindBy;
17 | import org.springframework.beans.factory.annotation.Autowired;
18 |
19 | import java.lang.reflect.Field;
20 | import java.util.regex.Matcher;
21 | import java.util.regex.Pattern;
22 | import java.util.stream.IntStream;
23 |
24 | /**
25 | * H5页面基类
26 | * Created by liuzhaoming on 2016/12/6.
27 | */
28 | @Slf4j
29 | @SuppressWarnings({"unused", "WeakerAcess"})
30 | public abstract class Html5Page extends AppiumBasePage {
31 | /**
32 | * 安卓控件边界正则表达式
33 | */
34 | private static final Pattern ANDROID_BOUNDS_PATTERN = Pattern.compile("\\[\\d+,\\d+\\]\\[\\d+,\\d+\\]");
35 |
36 | /**
37 | * IOS控件边界正则表达式
38 | */
39 | private static final Pattern IOS_BOUNDS_PATTERN = Pattern.compile("x=\"[\\d]+\" y=\"[\\d]+\" width=\"[\\d]+\" " +
40 | "height=\"[\\d]+\"");
41 |
42 | /**
43 | * 数值正则表达式
44 | */
45 | private static final Pattern NUMBER_PATTERN = Pattern.compile("[\\d]+");
46 |
47 | private static final String NATIVE_APP = "NATIVE_APP";
48 |
49 | /**
50 | * 移动端WebView上方y坐标
51 | */
52 | private static int WEB_VIEW_TOP = 0;
53 |
54 | /**
55 | * 移动端WebView下方y坐标
56 | */
57 | private static int WEB_VIEW_BOTTOM = 0;
58 |
59 | @Autowired
60 | private TouchAction touchAction;
61 |
62 | /**
63 | * 在控件上点击键盘Enter键
64 | *
65 | * @param webElement Web元素
66 | */
67 | protected void pressEnterKey(WebElement webElement) {
68 | if (null != webElement) {
69 | webElement.sendKeys(Keys.ENTER);
70 | }
71 | }
72 |
73 | /**
74 | * Page init time
75 | *
76 | * @return 默认值为1
77 | */
78 | protected int getPageInitTime() {
79 | return 5;
80 | }
81 |
82 | /**
83 | * 后退到上个页面
84 | *
85 | * @param tClass 页面类
86 | * @param 页面类
87 | * @return 上一个页面
88 | */
89 | protected T back(Class tClass) {
90 | driver.navigate().back();
91 | return gotoPage(tClass);
92 | }
93 |
94 | /**
95 | * 前进到下一个页面
96 | *
97 | * @param tClass 页面类
98 | * @param 页面类
99 | * @return 下一个页面
100 | */
101 | protected T forward(Class tClass) {
102 | driver.navigate().forward();
103 |
104 | return gotoPage(tClass);
105 | }
106 |
107 | /**
108 | * 页面刷新
109 | *
110 | * @return 当前页面
111 | */
112 | @SuppressWarnings("unchecked")
113 | protected T refresh() {
114 | driver.navigate().refresh();
115 |
116 | return (T) gotoPage(this.getClass());
117 | }
118 |
119 | /**
120 | * 判断Page 元素是否存在, 如果是WebView,元素不存在并不会抛出异常
121 | *
122 | * @param webElement Page元素
123 | * @return boolean
124 | */
125 | protected boolean isExist(WebElement webElement) {
126 | if (null == webElement) {
127 | return false;
128 | }
129 |
130 | try {
131 | return webElement.isDisplayed();
132 | } catch (NoSuchElementException e) {
133 | return false;
134 | }
135 | }
136 |
137 | /**
138 | * Web原始click事件,将WebView元素位置转化位native位置进行点击,因为移动端H5存在300ms的点击延时,有些网站捕获了事件
139 | *
140 | * @param webElement webElement
141 | */
142 | protected void clickByNativePosition(WebElement webElement) {
143 | String originalContext = null;
144 | try {
145 | int screenWebViewWidth = ((Long) driver.executeScript("return window.innerWidth || document.body.clientWidth")).intValue();
146 | int screenWebViewHeight = ((Long) driver.executeScript("return window.innerHeight || document.body.clientHeight")).intValue();
147 |
148 | int elementWebViewX = webElement.getLocation().getX();
149 | int elementWebViewY = webElement.getLocation().getY();
150 | int height = webElement.getSize().getHeight();
151 |
152 | // Switching to Native view to use the native supported methods
153 | originalContext = driver.getContext();
154 | driver.context(NATIVE_APP);
155 | double screenWidth = driver.manage().window().getSize().getWidth();
156 | // double screenHeight = driver.manage().window().getSize().getHeight();
157 | double screenHeight = 1980;
158 |
159 | // Service URL bar height
160 | double serviceUrlBar = screenHeight * (0.135135);
161 | double relativeScreenViewHeight = screenHeight - serviceUrlBar;
162 |
163 | // From the WebView coordinates we will be calculating the native view coordinates using the width and
164 | // height
165 | int elementNativeViewX = (int) ((elementWebViewX * screenWidth) / screenWebViewWidth);
166 | double elementNativeViewY = ((elementWebViewY * relativeScreenViewHeight) / screenWebViewHeight);
167 | // Adding 1 just to remove the 0.9999999 error
168 | int relativeElementNativeViewY = (int) (elementNativeViewY + serviceUrlBar);
169 |
170 | if (driver instanceof IOSDriver) {
171 | touchAction.tap(PointOption.point(elementNativeViewX, relativeElementNativeViewY - height / 2)).perform();
172 | } else {
173 | touchAction.tap(PointOption.point(elementNativeViewX, relativeElementNativeViewY)).perform();
174 | }
175 | } catch (Exception e) {
176 | log.warn("Click web element has error {}", AutotestUtils.getWebElementDesc(webElement), e);
177 | } finally {
178 | if (null != originalContext) {
179 | driver.context(originalContext);
180 | }
181 | }
182 | }
183 |
184 | /**
185 | * Web按钮click事件, 将WebView元素位置转化位native位置进行点击,因为移动端H5存在300ms的点击延时,有些网站捕获了事件
186 | *
187 | * @param webElement webElement
188 | */
189 | protected void clickByNativeWebViewPosition(WebElement webElement) {
190 | String originalContext = null;
191 | try {
192 | int screenWebViewWidth = ((Long) driver.executeScript("return window.innerWidth || document.body.clientWidth")).intValue();
193 | int screenWebViewHeight = ((Long) driver.executeScript("return window.innerHeight || document.body.clientHeight")).intValue();
194 |
195 | int elementWebViewX = getWebElementCenterX(webElement);
196 | int elementWebViewY = getWebElementCenterY(webElement);
197 |
198 | // Switching to Native view to use the native supported methods
199 | originalContext = driver.getContext();
200 | driver.context(NATIVE_APP);
201 |
202 | Dimension size = driver.manage().window().getSize();
203 | double screenWidth = size.getWidth();
204 |
205 | if (driver instanceof IOSDriver) {
206 | calculateSafariWebViewY();
207 | } else {
208 | calculateChromeWebViewY();
209 | }
210 |
211 | int elementNativeViewX = (int) ((elementWebViewX * screenWidth) / screenWebViewWidth);
212 | int webViewNativeHeight = WEB_VIEW_BOTTOM - WEB_VIEW_TOP;
213 | int elementNativeViewY = WEB_VIEW_TOP + webViewNativeHeight * elementWebViewY /
214 | screenWebViewHeight;
215 | touchAction.tap(PointOption.point(elementNativeViewX, elementNativeViewY)).perform();
216 | } catch (Exception e) {
217 | log.warn("Click web element {} has error ", AutotestUtils.getWebElementDesc(webElement), e);
218 | } finally {
219 | if (null != originalContext) {
220 | driver.context(originalContext);
221 | }
222 | }
223 | }
224 |
225 | /**
226 | * 通过native app 元素点击实现click效果
227 | *
228 | * @param webElement webElement
229 | */
230 | protected void clickNativeElement(WebElement webElement) {
231 | String originContext = driver.getContext();
232 | try {
233 | String nativeId = null;
234 | Field[] allFields = getClass().getDeclaredFields();
235 | for (Field field : allFields) {
236 | field.setAccessible(true);
237 | if (field.get(this) == webElement) {
238 | FindBy annotation = field.getAnnotation(FindBy.class);
239 | nativeId = annotation.id();
240 | if (StringUtils.isBlank(nativeId)) {
241 | String xpath = annotation.xpath();
242 | String[] temps = xpath.replaceAll("[\\]\"]", "").split("\\[");
243 | temps = temps[temps.length - 1].split("=");
244 | nativeId = temps[temps.length - 1];
245 | }
246 | break;
247 | }
248 | }
249 | if (StringUtils.isBlank(nativeId)) {
250 | log.info("Cannot find native Id {}", nativeId);
251 | return;
252 | }
253 |
254 | if (!originContext.equals(NATIVE_APP)) {
255 | driver.context(NATIVE_APP);
256 | }
257 |
258 | driver.findElementById(nativeId).click();
259 | } catch (Exception e) {
260 | log.warn("nativeClick fail ", e);
261 | } finally {
262 | if (!originContext.equals(NATIVE_APP)) {
263 | driver.context(originContext);
264 | }
265 | }
266 | }
267 |
268 | /**
269 | * WebView 滚动
270 | *
271 | * @param during 毫秒时间
272 | */
273 | protected void webSwipeUp(int during) {
274 | int screenWebViewHeight = ((Long) driver.executeScript("return window.innerHeight || document.body" +
275 | ".clientHeight")).intValue();
276 | driver.executeScript(String.format("window.scrollBy(0, %d)", screenWebViewHeight / 2));
277 |
278 | sleepInMillTime(during);
279 | }
280 |
281 | /**
282 | * WebView 滚动
283 | *
284 | * @param during 毫秒时间
285 | */
286 | protected void webSwipeDown(int during) {
287 | int screenWebViewHeight = ((Long) driver.executeScript("return window.innerHeight || document.body" +
288 | ".clientHeight")).intValue();
289 | driver.executeScript(String.format("window.scrollBy(0, -%d)", screenWebViewHeight / 2));
290 |
291 | sleepInMillTime(during);
292 | }
293 |
294 | /**
295 | * 通过屏幕物理位置向左滑动
296 | *
297 | * @param webElement 元素
298 | * @param during 滑动时间
299 | */
300 | protected void swipeLeft(WebElement webElement, int during) {
301 | swipeLeft(webElement, during, 1);
302 | }
303 |
304 | /**
305 | * 通过屏幕物理位置向左滑动多次
306 | *
307 | * @param webElement 元素
308 | * @param duringInMills 滑动时间(ms)
309 | * @param times 滑动次数
310 | */
311 | protected void swipeLeft(WebElement webElement, int duringInMills, int times) {
312 | String originalContext = null;
313 | try {
314 | int screenWebViewWidth = ((Long) driver.executeScript("return window.innerWidth || document.body.clientWidth")).intValue();
315 | int screenWebViewHeight = ((Long) driver.executeScript("return window.innerHeight || document.body.clientHeight")).intValue();
316 | int elementWebViewX = getWebElementCenterX(webElement);
317 | int elementWebViewY = getWebElementCenterY(webElement);
318 | int elementWebWidth = webElement.getSize().getWidth();
319 |
320 | // Switching to Native view to use the native supported methods
321 | originalContext = driver.getContext();
322 | driver.context(NATIVE_APP);
323 |
324 | Dimension size = driver.manage().window().getSize();
325 | double screenWidth = size.getWidth();
326 |
327 | if (driver instanceof IOSDriver) {
328 | calculateSafariWebViewY();
329 | } else {
330 | calculateChromeWebViewY();
331 | }
332 |
333 | int elementNativeViewX = (int) ((elementWebViewX * screenWidth) / screenWebViewWidth);
334 | int webViewNativeHeight = WEB_VIEW_BOTTOM - WEB_VIEW_TOP;
335 | int elementNativeViewY = WEB_VIEW_TOP + webViewNativeHeight * elementWebViewY / screenWebViewHeight;
336 | int elementNativeWidth = (int) (elementWebWidth * screenWidth / screenWebViewWidth);
337 |
338 | PointOption startPoint = PointOption.point(elementNativeViewX + elementNativeWidth / 3, elementNativeViewY);
339 | PointOption finishPoint = PointOption.point(elementNativeViewX - elementNativeWidth / 3, elementNativeViewY);
340 | IntStream.range(0, times)
341 | .forEach(i -> swipe(startPoint, finishPoint, duringInMills));
342 |
343 | } catch (Exception e) {
344 | log.warn("Click web element has error ", e);
345 | } finally {
346 | if (null != originalContext) {
347 | driver.context(originalContext);
348 | }
349 | }
350 | }
351 |
352 | /**
353 | * 通过屏幕物理位置向右滑动
354 | *
355 | * @param webElement 元素
356 | * @param duringInMills 滑动时间(ms)
357 | */
358 | protected void swipeRight(WebElement webElement, int duringInMills) {
359 | swipeRight(webElement, duringInMills);
360 | }
361 |
362 | /**
363 | * 通过屏幕物理位置向右滑动多次
364 | *
365 | * @param webElement 元素
366 | * @param duringInMills 滑动时间(ms)
367 | * @param times 滑动次数
368 | */
369 | protected void swipeRight(WebElement webElement, int duringInMills, int times) {
370 | String originalContext = null;
371 | try {
372 | int screenWebViewWidth = ((Long) driver.executeScript("return window.innerWidth || document.body.clientWidth")).intValue();
373 | int screenWebViewHeight = ((Long) driver.executeScript("return window.innerHeight || document.body.clientHeight")).intValue();
374 | int elementWebViewX = getWebElementCenterX(webElement);
375 | int elementWebViewY = getWebElementCenterY(webElement);
376 | int elementWebWidth = webElement.getSize().getWidth();
377 |
378 | // Switching to Native view to use the native supported methods
379 | originalContext = driver.getContext();
380 | driver.context(NATIVE_APP);
381 |
382 | Dimension size = driver.manage().window().getSize();
383 | double screenWidth = size.getWidth();
384 |
385 | if (driver instanceof IOSDriver) {
386 | calculateSafariWebViewY();
387 | } else {
388 | calculateChromeWebViewY();
389 | }
390 |
391 | int elementNativeViewX = (int) ((elementWebViewX * screenWidth) / screenWebViewWidth);
392 | int webViewNativeHeight = WEB_VIEW_BOTTOM - WEB_VIEW_TOP;
393 | int elementNativeViewY = WEB_VIEW_TOP + webViewNativeHeight * elementWebViewY / screenWebViewHeight;
394 | int elementNativeWidth = (int) (elementWebWidth * screenWidth / screenWebViewWidth);
395 | PointOption startPoint = PointOption.point(elementNativeViewX - elementNativeWidth / 3, elementNativeViewY);
396 | PointOption finishPoint = PointOption.point(elementNativeViewX + elementNativeWidth / 3, elementNativeViewY);
397 |
398 | IntStream.range(0, times).forEach(i -> swipe(startPoint, finishPoint, duringInMills));
399 | } catch (Exception e) {
400 | log.warn("Click web element has error ", e);
401 | } finally {
402 | if (null != originalContext) {
403 | driver.context(originalContext);
404 | }
405 | }
406 | }
407 |
408 | /**
409 | * This Method for swipe up
410 | *
411 | * @param duringInMills 滑动速度 等待多少毫秒
412 | */
413 | protected void swipeUp(int duringInMills) {
414 | String originContext = driver.getContext();
415 | if (!originContext.equals(NATIVE_APP)) {
416 | driver.context(NATIVE_APP);
417 | }
418 |
419 | int width = driver.manage().window().getSize().width;
420 | int height = driver.manage().window().getSize().height;
421 | swipe(width / 2, height * 3 / 4, width / 2, height / 4, duringInMills);
422 |
423 | if (!originContext.equals(NATIVE_APP)) {
424 | driver.context(originContext);
425 | }
426 | }
427 |
428 | /**
429 | * This Method for swipe down
430 | *
431 | * @param duringInMills 滑动速度 等待多少毫秒
432 | */
433 | protected void swipeDown(int duringInMills) {
434 | String originContext = driver.getContext();
435 | if (!originContext.equals(NATIVE_APP)) {
436 | driver.context(NATIVE_APP);
437 | }
438 |
439 | int width = driver.manage().window().getSize().width;
440 | int height = driver.manage().window().getSize().height;
441 | swipe(width / 2, height / 4, width / 2, height * 3 / 4, duringInMills);
442 |
443 | if (!originContext.equals(NATIVE_APP)) {
444 | driver.context(originContext);
445 | }
446 | }
447 |
448 | /**
449 | * This Method for swipe up, 从屏幕中心点滑到顶部
450 | *
451 | * @param duringInMills 滑动速度 等待多少毫秒
452 | */
453 | protected void swipeUpToTop(int duringInMills) {
454 | String originContext = driver.getContext();
455 | if (!originContext.equals(NATIVE_APP)) {
456 | driver.context(NATIVE_APP);
457 | }
458 |
459 | int width = driver.manage().window().getSize().width;
460 | int height = driver.manage().window().getSize().height;
461 | swipe(width / 2, height / 2, width / 2, 0, duringInMills);
462 |
463 | if (!originContext.equals(NATIVE_APP)) {
464 | driver.context(originContext);
465 | }
466 | }
467 |
468 | /**
469 | * This Method for swipe down, 从屏幕顶部滑到中心点
470 | *
471 | * @param duringInMills 滑动速度 等待多少毫秒
472 | */
473 | protected void swipeDownFromTop(int duringInMills) {
474 | String originContext = driver.getContext();
475 | if (!originContext.equals(NATIVE_APP)) {
476 | driver.context(NATIVE_APP);
477 | }
478 |
479 | int width = driver.manage().window().getSize().width;
480 | int height = driver.manage().window().getSize().height;
481 | swipe(width / 2, 0, width / 2, height / 2, duringInMills);
482 |
483 | if (!originContext.equals(NATIVE_APP)) {
484 | driver.context(originContext);
485 | }
486 | }
487 |
488 | /**
489 | * 获取控件中心点X坐标
490 | *
491 | * @param webElement webElement
492 | * @return 中心点X坐标
493 | */
494 | private int getWebElementCenterX(WebElement webElement) {
495 | if (webElement instanceof MobileElement) {
496 | return ((MobileElement) webElement).getCenter().getX();
497 | } else {
498 | int width = webElement.getSize().getWidth();
499 | return webElement.getLocation().getX() + width / 2;
500 | }
501 | }
502 |
503 | /**
504 | * 获取控件中心点Y坐标
505 | *
506 | * @param webElement webElement
507 | * @return 中心点Y坐标
508 | */
509 | private int getWebElementCenterY(WebElement webElement) {
510 | if (webElement instanceof MobileElement) {
511 | return ((MobileElement) webElement).getCenter().getY();
512 | } else {
513 | int height = webElement.getSize().getHeight();
514 | return webElement.getLocation().getY() + height / 2;
515 | }
516 | }
517 |
518 | /**
519 | * 计算chrome WebView Y 坐标
520 | */
521 | private void calculateChromeWebViewY() {
522 | if (WEB_VIEW_TOP > 0) {
523 | return;
524 | }
525 |
526 | synchronized (Html5Page.class) {
527 | String originContextName = driver.getContext();
528 | if (!originContextName.equals(NATIVE_APP)) {
529 | driver.context(NATIVE_APP);
530 | }
531 |
532 | try {
533 | String pageSource = driver.getPageSource();
534 | String[] temps = pageSource.split(" 0) {
564 | return;
565 | }
566 |
567 | synchronized (Html5Page.class) {
568 | String originContextName = driver.getContext();
569 | if (!originContextName.equals(NATIVE_APP)) {
570 | driver.context(NATIVE_APP);
571 | }
572 |
573 | try {
574 | String pageSource = driver.getPageSource();
575 | String[] temps = pageSource.split("
2 |
4 |
5 | autotest-parent
6 | com.qianmi
7 | 2.0.0-SNAPSHOT
8 | ../pom.xml
9 |
10 | 4.0.0
11 |
12 | autotest-web
13 | ${project.artifactId}
14 | PC 端网页自动化测试框架
15 |
16 |
17 |
18 | org.seleniumhq.selenium
19 | selenium-java
20 |
21 |
22 | com.qianmi
23 | autotest-base
24 | ${project.version}
25 |
26 |
27 | org.projectlombok
28 | lombok
29 |
30 |
31 | commons-io
32 | commons-io
33 |
34 |
35 |
--------------------------------------------------------------------------------
/autotest-web/src/main/java/com/qianmi/autotest/web/WebTestApplication.java:
--------------------------------------------------------------------------------
1 | package com.qianmi.autotest.web;
2 |
3 | import com.qianmi.autotest.base.AbstractTestApplication;
4 | import com.qianmi.autotest.base.testng.DefaultReporter;
5 | import com.qianmi.autotest.base.testng.QmDingNotifier;
6 | import com.qianmi.autotest.base.testng.TestRetryListener;
7 | import com.qianmi.autotest.web.testng.ScreenShotListener;
8 | import com.qianmi.autotest.web.testng.WebTestRetryAnalyzer;
9 | import org.springframework.boot.autoconfigure.SpringBootApplication;
10 | import org.testng.ITestNGListener;
11 |
12 | import java.util.ArrayList;
13 | import java.util.List;
14 |
15 | /**
16 | * Web自动化测试主程序入口
17 | * Created by liuzhaoming on 2016/12/7.
18 | */
19 | @SpringBootApplication(scanBasePackages = {"com.qianmi.autotest", "${autotest.scanPackage:}"})
20 | public class WebTestApplication extends AbstractTestApplication {
21 | public static void main(String[] args) {
22 | WebTestApplication webApplication = new WebTestApplication();
23 | webApplication.runTest(args);
24 | }
25 |
26 | /**
27 | * 子类可以重新此方法
28 | *
29 | * @return List 监听器
30 | */
31 | @Override
32 | protected List getListeners() {
33 | List listeners = new ArrayList<>();
34 | listeners.add(new DefaultReporter());
35 |
36 | if (Boolean.valueOf(System.getProperty("screenshot"))) {
37 | listeners.add(new ScreenShotListener());
38 | }
39 |
40 | if (Boolean.valueOf(System.getProperty("testRetry"))) {
41 | listeners.add(new TestRetryListener(WebTestRetryAnalyzer.class));
42 | }
43 |
44 | if (Boolean.valueOf(System.getProperty("dingNotice"))) {
45 | listeners.add(new QmDingNotifier());
46 | }
47 | return listeners;
48 | }
49 | }
50 |
--------------------------------------------------------------------------------
/autotest-web/src/main/java/com/qianmi/autotest/web/common/WebAutotestProperties.java:
--------------------------------------------------------------------------------
1 | package com.qianmi.autotest.web.common;
2 |
3 | import com.qianmi.autotest.base.common.AutotestProperties;
4 | import lombok.Data;
5 | import lombok.EqualsAndHashCode;
6 | import lombok.ToString;
7 | import lombok.extern.slf4j.Slf4j;
8 | import org.apache.commons.collections4.MapUtils;
9 |
10 | import java.util.Properties;
11 |
12 | /**
13 | * Web 测试程序配置参数
14 | * Created by liuzhaoming on 2016/12/6.
15 | */
16 | @EqualsAndHashCode(callSuper = true)
17 | @ToString(callSuper = true)
18 | @Data
19 | @Slf4j
20 | public class WebAutotestProperties extends AutotestProperties {
21 | /**
22 | * driver配置属性
23 | */
24 | protected Properties driver;
25 |
26 | /**
27 | * browser配置属性
28 | */
29 | protected Properties browser;
30 |
31 | /**
32 | * 默认浏览器名称
33 | */
34 | protected String defaultBrowserName;
35 |
36 | /**
37 | * 设备配置信息
38 | */
39 | protected Properties browserConfig;
40 |
41 | /**
42 | * appium driver配置信息
43 | */
44 | protected Properties driverConfig;
45 |
46 | /**
47 | * 获取当前生效的设备名称
48 | *
49 | * @return 设备名称
50 | */
51 | public String getActiveBrowserName() {
52 | return System.getProperty("browserName", defaultBrowserName);
53 | }
54 |
55 | /**
56 | * 获取浏览器配置信息
57 | *
58 | * @return 浏览器配置信息
59 | */
60 | public Properties getBrowserConfig() {
61 | if (MapUtils.isEmpty(browserConfig)) {
62 | synchronized (this) {
63 | if (MapUtils.isEmpty(browserConfig)) {
64 | String browserName = getActiveBrowserName();
65 | Properties totalBrowserProperties = new Properties();
66 | String browserNamePrefix = browserName + ".";
67 | log.info("Browser config bean {}", browserNamePrefix);
68 |
69 | browser.stringPropertyNames().stream()
70 | .filter(propertyName -> propertyName.startsWith(browserNamePrefix))
71 | .forEach(propertyName ->
72 | totalBrowserProperties.setProperty(propertyName.substring(browserNamePrefix.length()),
73 | browser.getProperty(propertyName))
74 | );
75 | browserConfig = totalBrowserProperties;
76 | }
77 | }
78 | }
79 |
80 | return browserConfig;
81 | }
82 |
83 | /**
84 | * 获取驱动配置信息
85 | *
86 | * @return 驱动配置信息
87 | */
88 | public Properties getDriverConfig() {
89 | if (MapUtils.isEmpty(driverConfig)) {
90 | synchronized (this) {
91 | if (MapUtils.isEmpty(driverConfig)) {
92 | String browserName = getActiveBrowserName();
93 | Properties totalDriverProperties = new Properties(driver);
94 | String browserNamePrefix = browserName + ".driver.";
95 | log.info("Driver config bean {}", browserNamePrefix);
96 |
97 | if (MapUtils.isNotEmpty(browser)) {
98 | browser.stringPropertyNames().stream()
99 | .filter(propertyName -> propertyName.startsWith(browserNamePrefix))
100 | .forEach(propertyName ->
101 | totalDriverProperties.setProperty(propertyName.substring(browserNamePrefix.length()),
102 | browser.getProperty(propertyName))
103 | );
104 | }
105 | driverConfig = totalDriverProperties;
106 | }
107 | }
108 | }
109 |
110 | return driverConfig;
111 | }
112 | }
113 |
--------------------------------------------------------------------------------
/autotest-web/src/main/java/com/qianmi/autotest/web/common/WebPageTest.java:
--------------------------------------------------------------------------------
1 | package com.qianmi.autotest.web.common;
2 |
3 | import com.qianmi.autotest.base.common.AutotestUtils;
4 | import com.qianmi.autotest.base.common.BasePageTest;
5 | import com.qianmi.autotest.base.common.BeanFactory;
6 | import com.qianmi.autotest.base.page.AppLoginPage;
7 | import com.qianmi.autotest.web.WebTestApplication;
8 | import com.qianmi.autotest.web.data.DataProvider;
9 | import com.qianmi.autotest.web.page.WebPageFacade;
10 | import lombok.extern.slf4j.Slf4j;
11 | import org.openqa.selenium.WebDriver;
12 | import org.springframework.beans.factory.annotation.Autowired;
13 | import org.springframework.boot.test.context.SpringBootTest;
14 | import org.testng.annotations.BeforeMethod;
15 |
16 | import javax.annotation.Resource;
17 | import java.lang.reflect.Method;
18 |
19 | /**
20 | * Web网页测试类基类
21 | * Created by liuzhaoming on 2016/12/6.
22 | */
23 | @SuppressWarnings({"WeakerAccess", "unused"})
24 | @Slf4j
25 | @SpringBootTest(classes = WebTestApplication.class)
26 | public class WebPageTest extends BasePageTest {
27 | @Autowired
28 | protected WebPageFacade pageFacade;
29 |
30 | @Autowired
31 | protected WebDriver webDriver;
32 |
33 | @Resource
34 | protected DataProvider inputData;
35 |
36 | @Resource
37 | protected DataProvider outputData;
38 |
39 | @BeforeMethod
40 | public void login(Method method) {
41 | String sceneName = AutotestUtils.getSceneName(method);
42 | String userName = inputData.getProperty("userName", sceneName);
43 | String password = inputData.getProperty("password", sceneName);
44 | String startUrl = inputData.getProperty("startUrl", sceneName);
45 | try {
46 | log.info("Begin login startUrl={} userName={}", startUrl, userName);
47 | webDriver.get(startUrl);
48 | AutotestUtils.sleep(1000);
49 | AppLoginPage loginPage = BeanFactory.getBean(AppLoginPage.class);
50 | if (null != loginPage) {
51 | loginPage.login(userName, password);
52 | }
53 | } catch (Exception e) {
54 | log.warn("Login has error", e);
55 | }
56 | }
57 | }
58 |
--------------------------------------------------------------------------------
/autotest-web/src/main/java/com/qianmi/autotest/web/common/WebResourceLoader.java:
--------------------------------------------------------------------------------
1 | package com.qianmi.autotest.web.common;
2 |
3 | import com.qianmi.autotest.base.common.AutotestException;
4 | import com.qianmi.autotest.base.common.BeanFactory;
5 | import lombok.extern.slf4j.Slf4j;
6 | import org.openqa.selenium.WebDriver;
7 | import org.openqa.selenium.chrome.ChromeDriver;
8 | import org.openqa.selenium.chrome.ChromeOptions;
9 | import org.openqa.selenium.edge.EdgeDriver;
10 | import org.openqa.selenium.edge.EdgeOptions;
11 | import org.openqa.selenium.firefox.FirefoxDriver;
12 | import org.openqa.selenium.firefox.FirefoxOptions;
13 | import org.openqa.selenium.ie.InternetExplorerDriver;
14 | import org.openqa.selenium.ie.InternetExplorerOptions;
15 | import org.openqa.selenium.remote.DesiredCapabilities;
16 | import org.openqa.selenium.safari.SafariDriver;
17 | import org.openqa.selenium.safari.SafariOptions;
18 | import org.springframework.beans.factory.annotation.Autowired;
19 | import org.springframework.boot.context.properties.ConfigurationProperties;
20 | import org.springframework.context.annotation.Bean;
21 | import org.springframework.stereotype.Component;
22 |
23 | import javax.annotation.PostConstruct;
24 | import java.util.Properties;
25 |
26 | /**
27 | * 系统资源加载
28 | * Created by liuzhaoming on 2016/12/6.
29 | */
30 | @SuppressWarnings("WeakerAccess")
31 | @Slf4j
32 | @Component
33 | public class WebResourceLoader {
34 |
35 | @Autowired
36 | private WebAutotestProperties webAutotestProperties;
37 |
38 | @Bean
39 | @ConfigurationProperties("autotest")
40 | public WebAutotestProperties webAutotestProperties() {
41 | return new WebAutotestProperties();
42 | }
43 |
44 | @PostConstruct
45 | public void printStartLog() {
46 | log.warn("~~~~~||~~~~~ Start test by browserName : {}", webAutotestProperties.getActiveBrowserName());
47 | }
48 |
49 | /**
50 | * 页面跳转
51 | *
52 | * @param url 要跳转的页面URL
53 | */
54 | public void gotoPage(String url) {
55 | try {
56 | WebDriver webDriver = getWebDriver();
57 |
58 | if (null == webDriver) {
59 | log.error("Goto page fail because web server is null");
60 | return;
61 | }
62 |
63 | webDriver.get(url);
64 | } catch (Exception e) {
65 | log.error("Cannot gotoPage {}", url, e);
66 | }
67 | }
68 |
69 | @Bean
70 | public WebDriver webDriver() {
71 | DesiredCapabilities capabilities = new DesiredCapabilities();
72 | Properties driverConfig = webAutotestProperties.getDriverConfig();
73 | for (String name : driverConfig.stringPropertyNames()) {
74 | setCapabilities(name, driverConfig.getProperty(name), capabilities);
75 | }
76 |
77 | try {
78 | WebDriver webDriver;
79 | String browserName = webAutotestProperties.getActiveBrowserName();
80 | if (browserName.equalsIgnoreCase("Chrome")) {
81 | ChromeOptions options = new ChromeOptions().merge(capabilities);
82 | webDriver = new ChromeDriver(options);
83 | } else if (browserName.equalsIgnoreCase("Firefox")) {
84 | FirefoxOptions options = new FirefoxOptions().merge(capabilities);
85 | webDriver = new FirefoxDriver(options);
86 | } else if (browserName.equalsIgnoreCase("IE")) {
87 | InternetExplorerOptions options = new InternetExplorerOptions().merge(capabilities);
88 | webDriver = new InternetExplorerDriver(options);
89 | } else if (browserName.equalsIgnoreCase("Safari")) {
90 | SafariOptions options = new SafariOptions().merge(capabilities);
91 | webDriver = new SafariDriver(options);
92 | } else if (browserName.equalsIgnoreCase("Edge")) {
93 | EdgeOptions options = new EdgeOptions().merge(capabilities);
94 | webDriver = new EdgeDriver(options);
95 | } else {
96 | log.error("Create web driver fail, because the browserName is null or default.browserName is null");
97 | throw new AutotestException();
98 | }
99 |
100 | // webDriver.manage().window().maximize();
101 | return webDriver;
102 | } catch (Exception e) {
103 | log.error("Create web driver fail", e);
104 | throw new AutotestException("Create appium driver fail ");
105 | }
106 | }
107 |
108 |
109 | /**
110 | * 获取Selenium 驱动
111 | *
112 | * @return Selenium驱动
113 | */
114 | protected WebDriver getWebDriver() {
115 | return BeanFactory.getBeanByType(WebDriver.class);
116 | }
117 |
118 | /**
119 | * 设置配置属性
120 | *
121 | * @param name 属性名称
122 | * @param value 属性值
123 | * @param capabilities 配置容器
124 | */
125 | protected void setCapabilities(String name, String value, DesiredCapabilities capabilities) {
126 | if (null == value) {
127 | return;
128 | }
129 |
130 | capabilities.setCapability(name, value);
131 | }
132 | }
133 |
--------------------------------------------------------------------------------
/autotest-web/src/main/java/com/qianmi/autotest/web/data/DataInitialization.java:
--------------------------------------------------------------------------------
1 | package com.qianmi.autotest.web.data;
2 |
3 | import com.qianmi.autotest.web.common.WebAutotestProperties;
4 | import lombok.Data;
5 | import lombok.extern.slf4j.Slf4j;
6 | import org.springframework.beans.factory.annotation.Autowired;
7 | import org.springframework.boot.context.properties.ConfigurationProperties;
8 | import org.springframework.context.annotation.Bean;
9 | import org.springframework.context.annotation.PropertySource;
10 | import org.springframework.stereotype.Component;
11 |
12 | import java.util.Properties;
13 |
14 | /**
15 | * 测试数据
16 | * Created by liuzhaoming on 16/9/27.
17 | */
18 |
19 | @Data
20 | @Slf4j
21 | @Component
22 | @ConfigurationProperties
23 | @PropertySource("classpath:data.properties")
24 | public class DataInitialization {
25 |
26 | private Properties input;
27 |
28 | private Properties output;
29 |
30 | private Properties browser;
31 |
32 | @Autowired
33 | private WebAutotestProperties webAutotestProperties;
34 |
35 | @Bean
36 | public DataProvider inputData() {
37 | Properties totalInputProperties = new Properties();
38 | if (null != input) {
39 | totalInputProperties.putAll(input);
40 | }
41 |
42 | String browserNamePrefix = webAutotestProperties.getActiveBrowserName() + ".input.";
43 | log.info("Input data bean browser name prefix is {}", browserNamePrefix);
44 | if (null != browser) {
45 | browser.stringPropertyNames().stream()
46 | .filter(propertyName -> propertyName.startsWith(browserNamePrefix))
47 | .forEach(propertyName ->
48 | totalInputProperties.setProperty(propertyName.substring(browserNamePrefix.length()),
49 | browser.getProperty(propertyName))
50 | );
51 | }
52 | return new DataProvider(totalInputProperties);
53 | }
54 |
55 | @Bean
56 | public DataProvider outputData() {
57 | Properties totalOutputProperties = new Properties();
58 | if (null != output) {
59 | totalOutputProperties.putAll(output);
60 | }
61 |
62 | String browserNamePrefix = webAutotestProperties.getActiveBrowserName() + ".output.";
63 | log.info("Output data bean browser name prefix is {}", browserNamePrefix);
64 | if (null != browser) {
65 | browser.stringPropertyNames().stream()
66 | .filter(propertyName -> propertyName.startsWith(browserNamePrefix))
67 | .forEach(propertyName ->
68 | totalOutputProperties.setProperty(propertyName.substring(browserNamePrefix.length()),
69 | browser.getProperty(propertyName))
70 | );
71 | }
72 | return new DataProvider(totalOutputProperties);
73 | }
74 | }
75 |
--------------------------------------------------------------------------------
/autotest-web/src/main/java/com/qianmi/autotest/web/data/DataProvider.java:
--------------------------------------------------------------------------------
1 | package com.qianmi.autotest.web.data;
2 |
3 | import java.io.Serializable;
4 | import java.util.Properties;
5 |
6 | /**
7 | * 测试数据封装类
8 | * Created by liuzhaoming on 2016/11/14.
9 | */
10 | @SuppressWarnings("unused")
11 | public class DataProvider implements Serializable {
12 | private Properties originData;
13 |
14 | public DataProvider(Properties originData) {
15 | this.originData = originData;
16 | }
17 |
18 | public String getProperty(String name) {
19 | return originData.getProperty(name);
20 | }
21 |
22 | public String getProperty(String name, String sceneName) {
23 | String propertyName = String.format("scene.%s.%s", sceneName, name);
24 | if (originData.containsKey(propertyName)) {
25 | return originData.getProperty(propertyName);
26 | }
27 |
28 | return originData.getProperty(name);
29 | }
30 | }
31 |
--------------------------------------------------------------------------------
/autotest-web/src/main/java/com/qianmi/autotest/web/page/WebBasePage.java:
--------------------------------------------------------------------------------
1 | package com.qianmi.autotest.web.page;
2 |
3 | import com.qianmi.autotest.base.common.BeanFactory;
4 | import com.qianmi.autotest.base.page.PageObject;
5 | import com.qianmi.autotest.web.common.WebAutotestProperties;
6 | import lombok.extern.slf4j.Slf4j;
7 | import org.openqa.selenium.*;
8 | import org.openqa.selenium.interactions.internal.Coordinates;
9 | import org.openqa.selenium.interactions.internal.Locatable;
10 | import org.openqa.selenium.remote.RemoteWebDriver;
11 | import org.openqa.selenium.support.PageFactory;
12 | import org.openqa.selenium.support.ui.ExpectedCondition;
13 | import org.openqa.selenium.support.ui.WebDriverWait;
14 | import org.springframework.beans.factory.annotation.Autowired;
15 |
16 | import javax.annotation.Nullable;
17 |
18 | /**
19 | * Web测试基类
20 | * Created by liuzhaoming on 16/9/23.
21 | */
22 | @SuppressWarnings({"unused", "WeakerAccess"})
23 | @Slf4j
24 | public abstract class WebBasePage implements PageObject {
25 | @Autowired
26 | protected RemoteWebDriver driver;
27 |
28 | @Autowired
29 | private WebAutotestProperties webProperties;
30 |
31 |
32 | /**
33 | * 页面跳转
34 | *
35 | * @param tClass Page Class
36 | * @param 泛型
37 | * @return Page页面
38 | */
39 | public T gotoPage(Class tClass) {
40 | log.info("Begin goto page " + tClass.getName());
41 | T page = BeanFactory.getBean(tClass);
42 | PageFactory.initElements(driver, page);
43 | page.afterConstruct();
44 | return page;
45 | }
46 |
47 | /**
48 | * 初始化页面
49 | *
50 | * @param page page
51 | * @param 泛型
52 | * @return 页面
53 | */
54 | protected T initPage(T page) {
55 | PageFactory.initElements(driver, page);
56 | page.afterConstruct();
57 | return page;
58 | }
59 |
60 | /**
61 | * 线程阻塞,供页面渲染
62 | */
63 | protected void sleep() {
64 | sleepInMillTime(webProperties.getPageLoadTimeInMills());
65 | }
66 |
67 | /**
68 | * Page元素构造完成后需要执行的操作
69 | */
70 | protected void afterConstruct() {
71 |
72 | }
73 |
74 | /**
75 | * 等待Page加载某个元素或者查询条件完成
76 | *
77 | * @param id 元素id
78 | * @return WebElement
79 | */
80 | protected WebElement wait(String id) {
81 | return new WebDriverWait(driver, webProperties.getElementLoadTimeInMills() / 1000, webProperties.getRefreshIntervalInMills())
82 | .until((ExpectedCondition) driver -> {
83 | try {
84 | return driver.findElement(By.id(id));
85 | } catch (Exception e) {
86 | return null;
87 | }
88 | });
89 | }
90 |
91 | /**
92 | * 等待Page加载某个元素
93 | *
94 | * @param webElement 页面元素
95 | * @return WebElement
96 | */
97 | protected WebElement wait(WebElement webElement) {
98 | return wait(webElement, webProperties.getElementLoadTimeInMills());
99 | }
100 |
101 | /**
102 | * 等待Page加载某个元素
103 | *
104 | * @param webElement 页面元素
105 | * @param timeOutInMills 最大等待时间,毫秒值
106 | * @return WebElement
107 | */
108 | protected WebElement wait(WebElement webElement, int timeOutInMills) {
109 | new WebDriverWait(driver, timeOutInMills / 1000, webProperties.getRefreshIntervalInMills())
110 | .until(new ExpectedCondition() {
111 | @Nullable
112 | @Override
113 | public WebElement apply(@Nullable WebDriver driver) {
114 | if (isExist(webElement)) {
115 | return webElement;
116 | } else {
117 | return null;
118 | }
119 | }
120 | });
121 |
122 | return webElement;
123 | }
124 |
125 | /**
126 | * 判断Page 元素是否存在
127 | *
128 | * @param webElement Page元素
129 | * @return boolean
130 | */
131 | protected boolean isExist(WebElement webElement) {
132 | if (null == webElement) {
133 | return false;
134 | }
135 |
136 | try {
137 | webElement.isDisplayed();
138 | return true;
139 | } catch (NoSuchElementException e) {
140 | return false;
141 | }
142 | }
143 |
144 |
145 | /**
146 | * 判断Page 元素是否存在
147 | *
148 | * @param webElement Page元素
149 | * @param timeOutInMills 超时时间, 毫秒值
150 | * @return boolean
151 | */
152 | protected boolean isExist(WebElement webElement, int timeOutInMills) {
153 | if (null == webElement) {
154 | return false;
155 | }
156 |
157 | try {
158 | WebElement element = new WebDriverWait(driver, timeOutInMills, webProperties.getRefreshIntervalInMills())
159 | .until((ExpectedCondition) driver -> {
160 | if (isExist(webElement)) {
161 | return webElement;
162 | } else {
163 | return null;
164 | }
165 | });
166 |
167 | return null != element;
168 | } catch (Exception e) {
169 | return false;
170 | }
171 | }
172 |
173 | /**
174 | * 网页滚动到指定的位置
175 | *
176 | * @param xCoordinate x坐标
177 | * @param yCoordinate y坐标
178 | */
179 | protected void scrollTo(int xCoordinate, int yCoordinate) {
180 | String script = String.format("window.scrollBy(%d, %d)", xCoordinate, yCoordinate);
181 | driver.executeScript("window.scrollBy(0, 700)");
182 | }
183 |
184 | /**
185 | * 网页滚动到指定的元素位置
186 | *
187 | * @param webElement 元素
188 | */
189 | protected void scrollTo(WebElement webElement) {
190 | Coordinates coordinates = ((Locatable) webElement).getCoordinates();
191 | coordinates.inViewPort();
192 | }
193 |
194 | /**
195 | * 在控件上点击键盘Enter键
196 | *
197 | * @param webElement Web元素
198 | */
199 | protected void pressEnterKey(WebElement webElement) {
200 | if (null != webElement) {
201 | webElement.sendKeys(Keys.ENTER);
202 | }
203 | }
204 |
205 | /**
206 | * 后退到上个页面
207 | *
208 | * @param tClass 页面类
209 | * @param 页面类
210 | * @return 上一个页面
211 | */
212 | protected T back(Class tClass) {
213 | driver.navigate().back();
214 | return gotoPage(tClass);
215 | }
216 |
217 | /**
218 | * 前进到下一个页面
219 | *
220 | * @param tClass 页面类
221 | * @param 页面类
222 | * @return 下一个页面
223 | */
224 | protected T forward(Class tClass) {
225 | driver.navigate().forward();
226 | return gotoPage(tClass);
227 | }
228 |
229 | /**
230 | * 页面刷新
231 | *
232 | * @return 当前页面
233 | */
234 | @SuppressWarnings("unchecked")
235 | protected T refresh() {
236 | driver.navigate().refresh();
237 | return (T) gotoPage(this.getClass());
238 | }
239 |
240 | /**
241 | * 向上滚动
242 | *
243 | * @param duringInMills 毫秒时间
244 | */
245 | protected void swipeUp(int duringInMills) {
246 | JavascriptExecutor jsExecutor = driver;
247 | int screenWebViewHeight = ((Long) jsExecutor.executeScript("return window.innerHeight || document.body.clientHeight")).intValue();
248 | jsExecutor.executeScript(String.format("window.scrollBy(0, %d)", screenWebViewHeight / 2));
249 |
250 | sleepInMillTime(duringInMills);
251 | }
252 |
253 | /**
254 | * 向下滚动
255 | *
256 | * @param duringInMills 毫秒时间
257 | */
258 | protected void swipeDown(int duringInMills) {
259 | JavascriptExecutor jsExecutor = driver;
260 | int screenWebViewHeight = ((Long) jsExecutor.executeScript("return window.innerHeight || document.body.clientHeight")).intValue();
261 | jsExecutor.executeScript(String.format("window.scrollBy(0, -%d)", screenWebViewHeight / 2));
262 |
263 | sleepInMillTime(duringInMills);
264 | }
265 | }
266 |
--------------------------------------------------------------------------------
/autotest-web/src/main/java/com/qianmi/autotest/web/page/WebPageFacade.java:
--------------------------------------------------------------------------------
1 | package com.qianmi.autotest.web.page;
2 |
3 | import com.qianmi.autotest.base.common.BeanFactory;
4 | import com.qianmi.autotest.base.common.Logoutable;
5 | import lombok.extern.slf4j.Slf4j;
6 | import org.springframework.stereotype.Component;
7 |
8 | /**
9 | * Web页面门户
10 | * Created by liuzhaoming on 16/9/26.
11 | */
12 | @SuppressWarnings("unused")
13 | @Slf4j
14 | @Component
15 | public class WebPageFacade extends WebBasePage {
16 |
17 | /**
18 | * 退出当前登录用户
19 | */
20 | public void logout() {
21 | Logoutable logoutable = BeanFactory.getBeanByType(Logoutable.class);
22 | if (null != logoutable) {
23 | logoutable.logout();
24 | } else {
25 | log.info("Cannot find Logoutable bean");
26 | }
27 | }
28 | }
29 |
--------------------------------------------------------------------------------
/autotest-web/src/main/java/com/qianmi/autotest/web/testng/ScreenShotListener.java:
--------------------------------------------------------------------------------
1 | package com.qianmi.autotest.web.testng;
2 |
3 | import com.qianmi.autotest.base.common.BeanFactory;
4 | import lombok.extern.slf4j.Slf4j;
5 | import org.apache.commons.io.FileUtils;
6 | import org.apache.commons.lang3.StringUtils;
7 | import org.openqa.selenium.OutputType;
8 | import org.openqa.selenium.TakesScreenshot;
9 | import org.openqa.selenium.WebDriver;
10 | import org.testng.ITestContext;
11 | import org.testng.ITestListener;
12 | import org.testng.ITestResult;
13 | import org.testng.Reporter;
14 |
15 | import java.io.File;
16 | import java.text.SimpleDateFormat;
17 | import java.util.Date;
18 |
19 | /**
20 | * 失败用例截屏
21 | * Created by liuzhaoming on 16/10/10.
22 | */
23 | @Slf4j
24 | public class ScreenShotListener implements ITestListener {
25 |
26 | /**
27 | * Invoked each time before a test will be invoked.
28 | * The ITestResult
is only partially filled with the references to
29 | * class, method, start millis and status.
30 | *
31 | * @param result the partially filled ITestResult
32 | * @see ITestResult#STARTED
33 | */
34 | @Override
35 | public void onTestStart(ITestResult result) {
36 |
37 | }
38 |
39 | /**
40 | * Invoked each time a test succeeds.
41 | *
42 | * @param result ITestResult
containing information about the run test
43 | * @see ITestResult#SUCCESS
44 | */
45 | @Override
46 | public void onTestSuccess(ITestResult result) {
47 | }
48 |
49 | /**
50 | * Invoked each time a test fails.
51 | *
52 | * @param result ITestResult
containing information about the run test
53 | * @see ITestResult#FAILURE
54 | */
55 | @Override
56 | public void onTestFailure(ITestResult result) {
57 | log.info("onTestFailure is called");
58 | saveScreenShot(result);
59 | }
60 |
61 | /**
62 | * Invoked each time a test is skipped.
63 | *
64 | * @param result ITestResult
containing information about the run test
65 | * @see ITestResult#SKIP
66 | */
67 | @Override
68 | public void onTestSkipped(ITestResult result) {
69 | }
70 |
71 | /**
72 | * Invoked each time a method fails but has been annotated with
73 | * successPercentage and this failure still keeps it within the
74 | * success percentage requested.
75 | *
76 | * @param result ITestResult
containing information about the run test
77 | * @see ITestResult#SUCCESS_PERCENTAGE_FAILURE
78 | */
79 | @Override
80 | public void onTestFailedButWithinSuccessPercentage(ITestResult result) {
81 |
82 | }
83 |
84 | /**
85 | * Invoked after the test class is instantiated and before
86 | * any configuration method is called.
87 | *
88 | * @param context context
89 | */
90 | @Override
91 | public void onStart(ITestContext context) {
92 |
93 | }
94 |
95 | /**
96 | * Invoked after all the tests have run and all their
97 | * Configuration methods have been called.
98 | *
99 | * @param context context
100 | */
101 | @Override
102 | public void onFinish(ITestContext context) {
103 |
104 | }
105 |
106 | private void saveScreenShot(ITestResult result) {
107 | SimpleDateFormat formatter = new SimpleDateFormat("yyyy-MM-dd-HH-mm-ss");
108 | String mDateTime = formatter.format(new Date());
109 | String fileName = mDateTime + "_" + result.getName();
110 | String filePath;
111 | try {
112 | WebDriver webDriver = BeanFactory.getBean(WebDriver.class);
113 | if (null == webDriver) {
114 | return;
115 | }
116 | File screenshot = ((TakesScreenshot) webDriver).getScreenshotAs(OutputType.FILE);
117 | filePath = new File(result.getTestContext().getOutputDirectory()).getParentFile().getAbsolutePath() +
118 | "/screenshot/" + fileName + ".jpg";
119 | File destFile = new File(filePath);
120 | FileUtils.copyFile(screenshot, destFile);
121 |
122 | } catch (Exception e) {
123 | log.error("Fail to save screenshot {}", fileName, e);
124 | return;
125 | }
126 |
127 | if (StringUtils.isNoneBlank(filePath)) {
128 | Reporter.setCurrentTestResult(result);
129 | Reporter.log("Not passed test " + result.getName() + " screenshot is : ");
130 | //把截图写入到Html报告中方便查看
131 | Reporter.log("
");
132 | }
133 | }
134 | }
135 |
--------------------------------------------------------------------------------
/autotest-web/src/main/java/com/qianmi/autotest/web/testng/WebTestRetryAnalyzer.java:
--------------------------------------------------------------------------------
1 | package com.qianmi.autotest.web.testng;
2 |
3 | import com.qianmi.autotest.base.common.AutotestUtils;
4 | import com.qianmi.autotest.base.common.BeanFactory;
5 | import com.qianmi.autotest.base.testng.BaseTestRetryAnalyzer;
6 | import com.qianmi.autotest.web.common.WebResourceLoader;
7 | import com.qianmi.autotest.web.data.DataProvider;
8 | import lombok.extern.slf4j.Slf4j;
9 | import org.testng.ITestResult;
10 |
11 | import java.lang.reflect.Method;
12 |
13 | /**
14 | * Web自动化测试程序失败重试接口
15 | * Created by liuzhaoming on 2016/12/8.
16 | */
17 | @Slf4j
18 | public class WebTestRetryAnalyzer extends BaseTestRetryAnalyzer {
19 | /**
20 | * 重启URL
21 | *
22 | * @param result 测试结果
23 | */
24 | @Override
25 | protected void restart(ITestResult result) {
26 | try {
27 | Method method = result.getMethod().getConstructorOrMethod().getMethod();
28 | String sceneName = AutotestUtils.getScene(method).value();
29 | DataProvider inputData = BeanFactory.getBean("inputData");
30 | String homeUrl = inputData.getProperty("homeUrl", sceneName);
31 |
32 | WebResourceLoader resourceContainer = BeanFactory.getBean(WebResourceLoader.class);
33 | log.info("Restart is called, and begin goto {}", homeUrl);
34 | resourceContainer.gotoPage(homeUrl);
35 | } catch (Exception e) {
36 | log.error("WebTestRetryAnalyzer restart app fail " + result.getMethod().getMethodName(), e);
37 | }
38 | }
39 | }
40 |
--------------------------------------------------------------------------------
/pom.xml:
--------------------------------------------------------------------------------
1 |
2 |
4 | 4.0.0
5 |
6 |
7 | org.springframework.boot
8 | spring-boot-starter-parent
9 | 1.5.7.RELEASE
10 |
11 |
12 | autotest-parent
13 | 2.0.0-SNAPSHOT
14 | pom
15 | ${project.artifactId}
16 | UI自动化测试框架,支持APP、Web、HTML5三端
17 |
18 |
19 | autotest-app
20 | autotest-html5
21 | autotest-base
22 | autotest-appium
23 | autotest-web
24 | autotest-demo
25 |
26 |
27 | 1.8
28 | 3.12.0
29 | 6.1.0
30 | 1.16.10
31 | 6.14.3
32 | 0.9.11
33 | 3.7
34 | 4.2
35 | 2.7
36 | git@github.com:jingpeicomp/autotest-framework.git
37 |
38 |
39 |
40 | scm:git:${git.url}
41 | scm:git:${git.url}
42 | ${git.url}
43 | HEAD
44 |
45 |
46 |
47 |
48 |
49 | org.projectlombok
50 | lombok
51 | ${lombok.version}
52 | provided
53 |
54 |
55 | org.testng
56 | testng
57 | ${testng.version}
58 |
59 |
60 | org.seleniumhq.selenium
61 | selenium-java
62 | ${selenium.version}
63 |
64 |
65 | org.reflections
66 | reflections
67 | ${reflections.version}
68 |
69 |
70 | io.appium
71 | java-client
72 | ${appium.version}
73 |
74 |
75 | org.apache.commons
76 | commons-lang3
77 | ${common.lang3.version}
78 |
79 |
80 | org.apache.commons
81 | commons-collections4
82 | ${collections.version}
83 |
84 |
85 | commons-io
86 | commons-io
87 | ${common.io.version}
88 |
89 |
90 |
91 |
92 |
93 |
94 |
95 | org.apache.maven.plugins
96 | maven-source-plugin
97 |
98 |
99 | org.apache.maven.plugins
100 | maven-release-plugin
101 |
102 | ${git.username}
103 | ${git.password}
104 |
105 |
106 |
107 | org.apache.maven.plugins
108 | maven-javadoc-plugin
109 |
110 | -Xdoclint:none
111 |
112 |
113 |
114 |
115 |
116 |
117 |
--------------------------------------------------------------------------------