├── app
└── qrcode-app.apk
├── .gitignore
├── LICENSE
├── README.md
├── pom.xml
└── src
└── test
└── java
└── com
└── eliasnogueira
└── qrcode
└── ReadQRCodeTest.java
/app/qrcode-app.apk:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/eliasnogueira/appium-read-qrcode/HEAD/app/qrcode-app.apk
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 | # Eclipse
2 | .classpath
3 | .project
4 | .settings/
5 |
6 | # Intellij
7 | .idea/
8 | *.iml
9 | *.iws
10 |
11 | # Mac
12 | .DS_Store
13 |
14 | # Maven
15 | log/
16 | target/
--------------------------------------------------------------------------------
/LICENSE:
--------------------------------------------------------------------------------
1 | MIT License
2 |
3 | Copyright (c) 2018 Elias Nogueira
4 |
5 | Permission is hereby granted, free of charge, to any person obtaining a copy
6 | of this software and associated documentation files (the "Software"), to deal
7 | in the Software without restriction, including without limitation the rights
8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9 | copies of the Software, and to permit persons to whom the Software is
10 | furnished to do so, subject to the following conditions:
11 |
12 | The above copyright notice and this permission notice shall be included in all
13 | copies or substantial portions of the Software.
14 |
15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21 | SOFTWARE.
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | # Appium Read QRCode
2 |
3 | This project shows how to read the QRCode content from an Android App using Appium and ZXing.
4 |
5 | ## Technologies in use
6 | * [Java](https://www.oracle.com/java/technologies/javase-downloads.html) as the programming language
7 | * [Appium](http://appium.io/) as the mobile test automation tool
8 | * [Zxing](https://github.com/zxing/zxing) as the library to decode the QRCode content
9 | * [AssertJ](https://joel-costigliola.github.io/assertj/) as the assertion library
10 | * [JUnit 5](https://junit.org/junit5/) as the test tool to support the test automation script
11 |
12 | ## How to run this project
13 |
14 | ### Preconditions
15 | - Java JDK >=11
16 | - Android Emulator with `minSdkVersion` used as 16 (Android 4.1 Jelly Bean)
17 |
18 | ### Steps
19 |
20 | ### Running using the command line
21 | 1. Go do the project directory and run `mvn verify -Dmaven.test.skip=true`
22 | 2. Run `mvn test` to run the test
23 |
24 | ### Running in your IDE
25 | 1. Open this project in your preferred IDE
26 | 2. Open the `ReadQRCodeTest` class placed in `src/test/java`
27 | 3. Run the test
28 |
29 | ### Expected result
30 | You can expect a successful execution.
31 | The test will read the QRCode content and assert by its expectation.
32 |
33 | ## What does the test do
34 | The code does the following:
35 | - Open an Android Emulator (if it's not opened)
36 | - Install the apk placed on `app` folder
37 | - Open the app main screen
38 | - Takes a screenshot of the screen that has the QrCode
39 | - Send the QRCode screenshot, as Base64, to be decoded by ZXing
40 | - Return the QRCode content
41 | - Assert the QRCode content
42 |
43 | **Attention**
44 | You need to have all the necessary configurations to run the test. This project has no intention to describe how you can do it.
--------------------------------------------------------------------------------
/pom.xml:
--------------------------------------------------------------------------------
1 |
2 |
5 | 4.0.0
6 |
7 | com.eliasnogueira
8 | appium-read-qrcode
9 | 1.0.0-SNAPSHOT
10 |
11 |
12 | 11
13 | UTF-8
14 | UTF-8
15 | 3.8.1
16 | 3.0.0-M5
17 |
18 | 5.8.0-M1
19 | 7.5.1
20 | 3.4.1
21 | 3.20.2
22 |
23 |
24 |
25 |
26 |
27 | org.junit.jupiter
28 | junit-jupiter
29 | ${junit.version}
30 |
31 |
32 |
33 | io.appium
34 | java-client
35 | ${appium.version}
36 |
37 |
38 |
39 | com.google.zxing
40 | javase
41 | ${zxing.version}
42 |
43 |
44 |
45 | org.assertj
46 | assertj-core
47 | ${assertj.version}
48 |
49 |
50 |
51 |
52 |
53 |
54 |
55 | org.apache.maven.plugins
56 | maven-compiler-plugin
57 | ${maven-compiler-plugin.version}
58 |
59 | ${java-compiler.version}
60 | ${java-compiler.version}
61 |
62 |
63 |
64 |
65 | org.apache.maven.plugins
66 | maven-surefire-plugin
67 | ${maven-surefire-plugin.version}
68 |
69 |
70 |
71 |
72 |
--------------------------------------------------------------------------------
/src/test/java/com/eliasnogueira/qrcode/ReadQRCodeTest.java:
--------------------------------------------------------------------------------
1 | package com.eliasnogueira.qrcode;/*
2 | * MIT License
3 | *
4 | * Copyright (c) 2018 Elias Nogueira
5 | *
6 | * Permission is hereby granted, free of charge, to any person obtaining a copy
7 | * of this software and associated documentation files (the "Software"), to deal
8 | * in the Software without restriction, including without limitation the rights
9 | * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
10 | * copies of the Software, and to permit persons to whom the Software is
11 | * furnished to do so, subject to the following conditions:
12 | *
13 | * The above copyright notice and this permission notice shall be included in all
14 | * copies or substantial portions of the Software.
15 | *
16 | * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
17 | * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
18 | * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
19 | * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
20 | * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
21 | * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
22 | * SOFTWARE.
23 | */
24 |
25 | import com.google.zxing.BinaryBitmap;
26 | import com.google.zxing.LuminanceSource;
27 | import com.google.zxing.MultiFormatReader;
28 | import com.google.zxing.NotFoundException;
29 | import com.google.zxing.Result;
30 | import com.google.zxing.client.j2se.BufferedImageLuminanceSource;
31 | import com.google.zxing.common.HybridBinarizer;
32 | import io.appium.java_client.AppiumDriver;
33 | import io.appium.java_client.MobileElement;
34 | import io.appium.java_client.android.AndroidDriver;
35 | import io.appium.java_client.remote.AutomationName;
36 | import io.appium.java_client.remote.MobileCapabilityType;
37 | import io.appium.java_client.remote.MobilePlatform;
38 | import io.appium.java_client.service.local.AppiumDriverLocalService;
39 | import io.appium.java_client.service.local.AppiumServiceBuilder;
40 | import org.junit.jupiter.api.AfterEach;
41 | import org.junit.jupiter.api.BeforeEach;
42 | import org.junit.jupiter.api.Test;
43 | import org.openqa.selenium.By;
44 | import org.openqa.selenium.OutputType;
45 | import org.openqa.selenium.Point;
46 | import org.openqa.selenium.remote.DesiredCapabilities;
47 | import org.slf4j.Logger;
48 | import org.slf4j.LoggerFactory;
49 |
50 | import javax.imageio.ImageIO;
51 | import java.awt.image.BufferedImage;
52 | import java.io.File;
53 | import java.io.IOException;
54 |
55 | import static org.assertj.core.api.Assertions.assertThat;
56 |
57 | class ReadQRCodeTest {
58 |
59 | private static final Logger log = LoggerFactory.getLogger(ReadQRCodeTest.class);
60 |
61 | private AppiumDriver driver;
62 | private AppiumDriverLocalService service;
63 |
64 | @BeforeEach
65 | public void setup() {
66 | DesiredCapabilities capabilities = new DesiredCapabilities();
67 | capabilities.setCapability(MobileCapabilityType.APP, new File("app/qrcode-app.apk").getAbsolutePath());
68 | capabilities.setCapability(MobileCapabilityType.AUTOMATION_NAME, AutomationName.ANDROID_UIAUTOMATOR2);
69 | capabilities.setCapability(MobileCapabilityType.PLATFORM_NAME, MobilePlatform.ANDROID);
70 | capabilities.setCapability(MobileCapabilityType.DEVICE_NAME, "Android Emulator");
71 |
72 | AppiumServiceBuilder builder = new AppiumServiceBuilder().usingAnyFreePort();
73 | service = AppiumDriverLocalService.buildService(builder);
74 | driver = new AndroidDriver<>(builder, capabilities);
75 |
76 | service.start();
77 | }
78 |
79 | @AfterEach
80 | public void tearDown() {
81 | driver.quit();
82 | service.stop();
83 | }
84 |
85 | /**
86 | * This test capture the screenshot and get the element that contains the QRCode
87 | * Based on the element points (width and height) the image os cropped
88 | * With cropped image we can decode the QRCode with zxing
89 | */
90 | @Test
91 | void readQRCode() {
92 | MobileElement qrCodeElement = driver.findElement(By.id("com.example.qrcode:id/imageView"));
93 | File screenshot = driver.getScreenshotAs(OutputType.FILE);
94 |
95 | String content = decodeQRCode(generateImage(qrCodeElement, screenshot));
96 | assertThat(content).isEqualTo("f3ce8d4d-074f-483f-9fd0-45c7947fd40c");
97 | }
98 |
99 | /**
100 | * Return a cropped image based on an element (in this case the qrcode image) from the entire device screenshot
101 | *
102 | * @param element elemement that will show in the screenshot
103 | * @param screenshot the entire device screenshot
104 | * @return a new image in BufferedImage object
105 | */
106 | private BufferedImage generateImage(MobileElement element, File screenshot) {
107 | BufferedImage qrCodeImage = null;
108 |
109 | try {
110 | BufferedImage fullImage = ImageIO.read(screenshot);
111 | Point imageLocation = element.getLocation();
112 |
113 | int qrCodeImageWidth = element.getSize().getWidth();
114 | int qrCodeImageHeight = element.getSize().getHeight();
115 |
116 | int pointXPosition = imageLocation.getX();
117 | int pointYPosition = imageLocation.getY();
118 |
119 | qrCodeImage = fullImage.getSubimage(pointXPosition, pointYPosition, qrCodeImageWidth, qrCodeImageHeight);
120 | ImageIO.write(qrCodeImage, "png", screenshot);
121 | } catch (IOException e) {
122 | log.error("Problem during the image generation", e);
123 | }
124 |
125 | return qrCodeImage;
126 | }
127 |
128 | /**
129 | * Decode a QR Code image using zxing
130 | *
131 | * @param qrCodeImage the qrcode image cropped from entire device screenshot
132 | * @return the content
133 | */
134 | private static String decodeQRCode(BufferedImage qrCodeImage) {
135 | Result result = null;
136 | try {
137 | LuminanceSource source = new BufferedImageLuminanceSource(qrCodeImage);
138 | BinaryBitmap bitmap = new BinaryBitmap(new HybridBinarizer(source));
139 |
140 | result = new MultiFormatReader().decode(bitmap);
141 | } catch (NotFoundException e) {
142 | log.error("QRCode not found", e);
143 | }
144 | return result.getText();
145 | }
146 | }
147 |
--------------------------------------------------------------------------------