This article is the 20th day article of Recruit Lifestyle Advent Calendar 2017.
My name is @AHA_oretama and my first child was born the day before yesterday. I've been busy with various private events, and I've been a little late in posting, but please forgive me!
I usually recruit and do an activity called R-SET. The word SET itself is a role advocated by Google and was introduced in Google Testing Blog. I am. The excerpt of the explanation part of SET is as follows.
The SET or Software Engineer in Test is also a developer role except their focus is on testability.
In summary, it is engineer focusing on testability → SET
.
R-SET is a coined word created to improve searchability and is defined as follows.
R-SET is Recruit Lifestyle's SET.
At R-SET, by increasing the testability of Recruit Lifestyle services
Is listed as a mission.
Now that we've introduced it, I'd like to talk about recording the UI test of the app.
First of all, make sure that you are aware of the UI test mentioned here.
Do you know the word test pyramid? ?? The test pyramid is a diagram showing the characteristics of the tests to be automated, and the UI tests, Integration tests, and Unit tests are arranged in order from the top. In general, UI test often refers to end-to-end test with all modules and external services connected.
This time, the one that matches this test pyramid is called the UI test.
As a characteristic of the pyramid, the size of the pyramid represents the ideal ratio of the amount of test cases for automation. In other words, it is said that it is ideal to make many Unit tests and not too many UI tests. It also has the characteristics of being unstable, taking longer to run tests, and more costly as you go up the pyramid.
And the characteristics of this UI test lead to the need for recording.
Automation has a number of benefits, one of which is cost savings. However, due to its nature, UI tests tend to be unstable tests. If you've done UI testing, you've experienced things like, sometimes the test succeeds and sometimes the test fails, even though you haven't changed the source. Isn't it? If a test fails, you will then need to identify the cause of the test failure, but it is an unstable test and will not always reappear if you run the failed test again. If you do not execute it over and over again, the same state cannot be reproduced and it will take time to investigate the cause. Another characteristic of UI tests is that they take a long time to run. The closer the failed test is to the end of the test scenario, the longer it will take to reach that state.
Did you notice? Even though the benefits of automation are cost savings, the maintenance of automated tests is costly and the benefits are lost!
But what if the failed test was recorded at this time?
For example
You will often be able to understand the cause.
In short, recording is a response to UI test instability and increased costs due to long execution times, which is necessary to effectively benefit from automation.
Now that the UI test has shown the importance of recording, let's talk about how to record. This time I will focus on apps, so I would like to introduce the WEB if there is another opportunity.
Recording on an Android device can be executed with the following ADB command.
adb shell screenrecord {filename}
You can stop it with Ctrl + C
.
Here, the output file is saved on the Android device.
Since filename is the file name on the Android device, it will be something like /sdcard/demo.mp4
.
I think that files saved on Android devices are often saved on the local PC. You can create a file on your Android device on your local PC with the following ADB command.
adb pull {remote} {local}
According to the User Guide, the recording function is supported for Android 4.4 (API level 19) or later. ,be careful. Also note that the maximum recording time is 180 seconds (3 minutes) at the maximum.
Recording of iOS Simulator can be executed by the following Xcode command-line utility command.
xcrun simctl io booted recordVideo {filename}
In the case of iOS Simulator, the save destination is the local PC.
iOS can also be stopped with Ctrl + C
.
According to the Release Notes Please note that this recording feature is a feature from Xcode 8.2.
We will implement the function of creating a UI test using Appium and recording the test in Java. While there are many Japanese documents for Ruby in Appium, there are few Japanese documents for other languages (although the original English site is also ...), so I think it makes sense to write Appium functions in Java. I will.
Here is a concrete implementation.
It is an environment just in case, but I have confirmed it in the following environment.
Let's introduce the implementation.
public class RecordFactory {
public static RecordFactory factory = new RecordFactory();
public static RecordFactory getInstance() {
return factory;
}
public Record createRecord(AppiumDriver driver, String fileName, String output) {
if (driver.getPlatformName().equals("Android")) {
return new AdbRecord(fileName, output);
} else {
return new iOSSimulatorRecord(fileName, output);
}
}
}
interface Record extends Closeable {
void start()
}
This is a description of the above code.
ʻAppiumDriver.getPlatformName ()can be used to get the execution terminal (Android or iOS). Appium has the advantage of being a cross-platform UI test tool, so here we are making it possible to use it regardless of the recording terminal by creating a Factory class that determines the running terminal and creates a class according to it. I will. Since there was an image of
Closeable` in the recording process, I added that interface, but I think that it is OK even if there is nothing in particular.
public class AdbRecord implements Record {
private final String fileName;
private final String outputDir;
private Process recordProcess;
private final String outputPath;
AdbRecord(String fileName,String outputDir) {
this.fileName = fileName.endsWith(".mp4") ? fileName : fileName + ".mp4";
this.outputDir = outputDir;
this.outputPath = outputDir + "/" + fileName;
}
@Override
public void start() throws IOException {
ProcessBuilder builder =
new ProcessBuilder("adb", "shell", "screenrecord", "/sdcard/" + fileName);
builder.redirectErrorStream(true);
recordProcess = builder.start();
}
@Override
public void close() throws IOException {
if (recordProcess != null) {
int pid = 0;
try {
Field field = recordProcess.getClass().getDeclaredField("pid");
field.setAccessible(true);
pid = field.getInt(recordProcess);
} catch (IllegalAccessException | NoSuchFieldException e) {
e.printStackTrace();
}
while(true) {
ProcessBuilder builder = new ProcessBuilder("kill", "-2", String.valueOf(pid));
builder.start();
TestUtils.sleep(1000);
if(!recordProcess.isAlive()) {
break;
}
}
}
File dir = new File(outputDir);
if(!dir.exists()) {
dir.mkdir();
}
//Sleep because there is a slight time lag until the video file is completed
TestUtils.sleep(3000);
//Copy video files on Android device locally
execProcess("adb", "pull", "/sdcard/" + fileName, outputPath);
//Delete video files on Android device
execProcess("adb", "shell", "rm", "-f","/sdcard/" + fileName);
try (InputStream stream = Files.newInputStream(Paths.get(outputPath))) {
Allure.addAttachment(fileName, stream);
}
}
private void execProcess(String... args) throws IOException {
ProcessBuilder builder = new ProcessBuilder(args);
Process process = builder.start();
try {
process.waitFor(20, TimeUnit.SECONDS);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
public class iOSSimulatorRecord implements Record {
private Process recordProcess;
private final String fileName;
private final File outputDir;
private final String outputPath;
iOSSimulatorRecord(String fileName, String outputDir) {
this.fileName = fileName.endsWith(".mov") ? fileName : fileName + ".mov";
this.outputDir = new File(outputDir);
this.outputPath = this.outputDir.getAbsolutePath() + "/" + fileName;
}
@Override
public void start() throws IOException {
if(!outputDir.exists()) {
outputDir.mkdir();
}
ProcessBuilder builder =
new ProcessBuilder("xcrun", "simctl", "io", "booted", "recordVideo", outputPath);
builder.redirectErrorStream(true);
recordProcess = builder.start();
}
@Override
public void close() throws IOException {
if (recordProcess != null) {
int pid = 0;
try {
Field field = recordProcess.getClass().getDeclaredField("pid");
field.setAccessible(true);
pid = field.getInt(recordProcess);
} catch (IllegalAccessException | NoSuchFieldException e) {
e.printStackTrace();
}
while(true) {
ProcessBuilder builder = new ProcessBuilder("kill", "-2", String.valueOf(pid));
builder.start();
TestUtils.sleep(1000);
if(!recordProcess.isAlive()) {
break;
}
}
}
}
}
For both Android and iOS, the recording process for each device is started using the Java Process class. In Java, the process of getting the pid is so complicated that it's complicated, but all you're doing is getting the pid. Android is different from iOS in that it has a process of recording and then saving it to the local PC.
The user can use it as follows.
try (Record record = recordFactory.createRecord(driver, fileName, BUILD_REPORTS_RECORDS)) {
record.start();
// "Run UI test"
}
In this way, you can also record in Java.
The development of test cases with Appium is still in its infancy, and I think that various things will come out in operation and development, so I would like to introduce it again as needed.
Recommended Posts