├── 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 | --------------------------------------------------------------------------------