简体中文 | English
Selenium 4 has come. I am modifying this project to fix with it. And almost done. If you use the old Selenium 3 still, go to the branch
backup-Selenium3
.
- This is a UI test automation framework for Web/Android/iOS/H5, and supports Android performance testing. The core of the project uses Selenium/Appium.
- The project uses Maven for project management and TestNG for test case management.
- The common Selenium APIs are encapsulated, such as
click()
,type()
, etc. And they are added element availability verification, log recording, and taking screenshot for failed tests. - WebUI, AndroidUI and iOSUI are separate modules. According to OS features, special base class methods have been
written,
and the configuration file
config.properties
under the module root directory will not affect each other. - A testcase can test online or offline environment at the same time by different tests data driven.
- Log and assertion have been encapsulated into LogUtil and AssertUtil, which is easy to replace or extend in the future.
- WebDriverManager carries out the management (i.e., download, setup, and maintenance) of the web drivers in a fully automated manner.
- Android: If
mapping.txt
the resource obfuscation file is provided, the framework can automatically replace the original element id with the obfuscated id, and then the positioning elements work fine.
- JDK >= 8 (The minimum Java version required is still 8.)
- Maven >= 3
- Selenium 4
- Appium (java-client 8)
- TestNG 6 -- TODO: to replace testNg with Junit
- IDE (Eclipse or others, this tutorial takes Eclipse as an example) + TestNG plugin + Maven plugin
- Install
Android SDK
1.1 Configure environment variables
1.2 Verifyadb
in the console whether it run ok or not
1.3 If you are about to run the test cases on emulators, you need to create emulators usingAVD Manager
- Install the Appium server program, see http://appium.io/. Recommend to install it in the way
of
Appium Desktop
.
- macOS is required!
1.1 Appium Server's operation, element recognition, and script execution all require the macOS
1.2 Install Xcode, verify the installation until the tested app can be compiled successfully and can be run in a simulator - If you want to run test case on a device, follow the manual Deploying an iOS app to a real device
- Install the Appium server program, after installation, you will find that it has more support for iOS testing than the Windows version
├─perftest_android <-- Android performance test module
│ ├─perfreport <-- Performance test result
│ └─src <-- test case
├─uitest_android <-- Android ui test module
│ ├─app <-- Store tested `apk` and `mapping` files
│ ├─testdata <-- test data,csv format
│ │ ├─online <-- test data for online
│ │ └─test <-- test data for test
│ ├─screenshot <-- Failure screenshot
│ └─src
│ └─main
│ └─java
│ └─com.quanql.test
│ ├─androidui
│ │ ├─base <-- Android-specific base class, extend from the base of uitest_core
│ │ ├─page <-- page object
│ │ └─testcase <-- test case
│ └─perfutils <-- Android performance test util
├─uitest_core
│ └─src
│ └─main
│ ├─java
│ │ └─com.quanql.test
│ │ └─core
│ │ ├─base
│ │ │ ├─BaseOpt.java <-- base class. such as click, type, findelement,screenShot etc.
│ │ │ ├─BasePage.java <-- page object base class
│ │ │ ├─BaseTest.java <-- test case base class. manage TestNG life cycle
│ │ │ └─DriverFactory.java <-- driver factory, web and android and other drivers are here
│ │ ├─listener
│ │ └─utils
│ └─resources <-- slf4j config file is here
└─uitest_web
└─src
└─main
├─java
│ └─com.quanql.test
│ └─webui
│ ├─base <-- web-specific base class, extend from the base of uitest_core
│ ├─page
│ └─testcase
└─resources <-- chromedriver is here
- Open browser, i.e. Chrome, open the url
- Click F12 to open up the
DevTools
window, find elements by it. Relative cssSelector expression is recommended, such as,button[aria-label='Google Search']
- page demo: See more details in
DemoBaiduPage.java
of uitest_web - case demo: See more details in
Demo163Test.java
of uitest_web
- configure
config.properties
- driver.type=chrome, firefox, iOSSafari
- running.type=local (Standalone mode), remote (hub-node mode)
- remote.address=the hub server url, only used as
running.type=remote
- Premise: Ensure that xcode can compile the app under test successfully, and verify the app's basic functions in the simulator
- Update the code
- Compile the app
> cd $APP_PATH
> xcodebuild -workspace quanqlAPP.xcworkspace -scheme quanql -configuration Debug -sdk iphonesimulator -arch x86_64
- Generate compressed package, where quanql.zip is the app under test
> ditto -ck --sequesterRsrc --keepParent \`ls -1 -d -t ~/Library/Developer/Xcode/DerivedData/\*/Build/Products/Debug-iphonesimulator/*.app | head -n 1\` ~/quanql.zip
- On the
iOS settings
page of appium server, setApp Path
to the directory of the zip generated in the previous step, then setForce Device
andPlatform Version
- Click
launch
- Click
inspector
. The operation of finding element is similar with Android - Relative XPath expression is recommended, such
as,
Button("xpath=//UIAAlert[@name='prompt']//UIAButton[@name='OK']")
- page demo: See more details in
LoginPage.java
of uitest_iphone - case demo: See more details in
DemoLoginTest.java
of uitest_iphone
- configure
config.properties
- remote.address=the path of appium server
- app=the absolute path of appium server
- appium client
- on Mac, that means you can run client and appium server on the same Mac
- OR, on PC, the client will send commands to the remote appium server, and then the cases are still run on appium server
Similar to iPhone.
Similar to web. In configure config.properties
, set driver.type=H5
so as to run test cases on mobile emulation
mode in Chrome.
I think the people involved in writing UI automation can be divided into two groups (although there may be teams that don't have two groups, all work together) :
- The first group: the people who write the underlying or basic functions. They may be responsible for writing the common methods, identification and encapsulation elements in base. People in this group may not understand the business, but require high programming skills;
- The second group: the people who write business test cases. They integrate pages and components according to business requirements to make them conform to a certain user story, and complete the transformation from manual business tests to automation. People in this group may be required to understand business, but need few programming skills. In addition, the pages and elements they use are provided by the first group of people.
The following describes the two ways provided in this project showing how to code. The first is more business-friendly, less technical, and more readable. However, personally, I'm more comfortable with the second one because it supports complex controls and scenarios better.
More details in ./uitest_iphone/src/main/java/com/quanql/test/iphoneui/page/LoginPage.java
and
./uitest_android/src/main/java/com/quanql/test/androidui/testcase/DemoLoginTest.java
The Login page
/**
* Note that there are nothing in this class except elements location.
*
* This is the FIRST difference from the other way.
*/
public class LoginPage extends IphoneBasePage {
public static Edit UserName = new Edit("xpath=//UIATextField[contains(@value,'type the phone number')]");
public static Edit PassWord = new Edit("xpath=//UIASecureTextField");
public static Button LoginBtn = new Button("xpath=//UIAButton[@name='Log in']");
public static Text LoginFailAlert = new Text("xpath=//UIAAlert[@name='prompt']//UIAStaticText[@name='incorrect username or password']");
public static Button AlertOKBtn = new Button("xpath=//UIAAlert[@name='prompt']//UIAButton[@name='OK']");
}
The Log in test case (using Log in page)
/**
* Explanation:
* 1. firstly locate certain element, e.g. LoginPage.UserName
* 2. secondly operate the element, e.g. LoginPage.UserName.sendkeys("111111"), type into something
*
* This is the SECOND difference from the other way.
*/
public class DemoLoginTest extends IphoneBaseTest {
@Test
public void testLogin() {
TabHomePage.wait(20000);
TabHomePage.TabMine.click();
TabMyPage.LoginNowBtn.click();
LoginPage.UserName.sendkeys("111111");
LoginPage.PassWord.sendkeys("2222");
LoginPage.LoginBtn.click();
AssertUtil.assertTrue(LoginPage.LoginFailAlert.isDisplayed(), "login failure dialog expects");
}
}
More details in ./uitest_web/src/main/java/com/quanql/test/webui/testcase/Demo163Test.java
Log in page
/**
* Note that not only elements' location but also elements' operation are in this class.
*/
public class DemoBaiduPage extends WebBasePage {
private static String edtSearchId = "kw";
private static String btnSearchXpath = "//input[@value='百度一下']";
private static String txtNetEaseXpath = "//div[@id='content_left']/div//a[contains(.,'www.163.com')]";
/**
* open Baidu
*/
public static void openBaidu() {
LogUtil.info("open Baidu homepage by the url");
baseOpt.open("https://www.baidu.com");
}
/**
* type in search bar
* @param name -
*/
public static void typeInSearchEdt(String name) {
LogUtil.info("Enter search content:" + name);
baseOpt.sendkeys(By.id(edtSearchId), name);
}
/**
* click search button
*/
public static void clickSearchBtn() {
LogUtil.info("click search button");
baseOpt.click(By.xpath(btnSearchXpath));
}
public static boolean isNetEaseExisted() {
LogUtil.info("verify the goal page is open");
return baseOpt.isElementDisplayed(By.xpath(txtNetEaseXpath));
}
}
The Log in test case (using Log in page)
/**
* Only the methods provided in the Page class can be called.
* It restricts the developer from writing bad code because the elements are used incorrectly.
*/
public class Demo163Test extends WebBaseTest {
@Test
public void testSearchNetEase() {
DemoBaiduPage.openBaidu();
DemoBaiduPage.typeInSearchEdt("NetEase");
DemoBaiduPage.clickSearchBtn();
AssertUtil.assertTrue(DemoBaiduPage.isNetEaseExisted(), "NetEase isn't in the first place of the results!");
}
}
See the example first
/**
* register
*/
public class NewUserRegisterTest extends AndroidBaseTest {
@Test(dataProvider = "providerMethod")
public void testNewUserRegister(String telephone, String passwd) {
TabHomePage.TabMine.click();
TabMyPage.MyRegister.click();
RegisterPage.TelPhoneNum.sendkeys(telephone);
RegisterPage.SetPassWord.sendkeys(passwd);
RegisterPage.NowRegisterBtn.click();
AssertUtil.assertTrue(RegisterPage.RegisterSucess.isDisplayed(), "fail to register");
}
}
## Running environment: online, test
## It determines which data to use, and the parameter name is also the directory where the data is stored
running.env=test
testdata
andsrc
at the same level- only .csv format supports currently
├─uitest_android
├─src
└─testdata
├─online <-- Online data
│ └─ NewPersonRegister.csv
└─test <-- Offline data
└─ NewPersonRegister.csv
Use text editor to open NewPersonRegister.csv
, you will see like the following, or you can open it in Excel:
telephone,passwd
18888888888,pwd123
- running.env in
config.properties
determines the data directory - the test method name annotated by
@Test
(i.e. testNewUserRegister in the above example) determines the data-driven filename NewPersonRegister.csv- the test method name must start with test, but _ is optional, e.g. testHelloWorld_01()
- combined with the path in
1.
, the full file directory in this example issubmodule_name/testdata/test/NewPersonRegister.csv
- test case
@Test
followed by(dataProvider = "providerMethod")
- the number and sort of parameters in testNewUserRegister() are same with the stuff in the csv file, however it's not necessary to keep parameter name and cvs header the same
I have a video courses series on how to use Selenium for Automation testing. if you're interested, click here 网易云课堂.
Reminder: the course is in Mandarin only.