Having some QR Codes to scan from your mobile and or web app is getting common nowadays. You can read to add a new contact to your phone, distribute it or even validate a financial transaction.
An approach to get the QR Code image is necessary to guarantee end-to-end (e2e) testing through an application where you need to read it.
There are two different approaches: one for the Mobile app and another one for the Web App.
We will use ZXing (zebra crossing) is an open-source, multi-format 1D/2D barcode image processing library implemented in Java, with ports to other languages. One supported 2D format is the QR Code.
This is the dependency we will use in the project:
<!-- https://mvnrepository.com/artifact/com.google.zxing/javase -->
<dependency>
<groupId>com.google.zxing</groupId>
<artifactId>javase</artifactId>
<version>3.4.1</version>
</dependency>
An easy solution to read the QR Code content in a mobile app is to take a screenshot from the device screen and crop it. We can send the image to Zxing to decode it.
First, as described above, we must take a screenshot of where the QR Code is. Then we need to crop the image to have only the QR Code in it. After that, we need to decode the image with Zxing to get its contents.
First, let us visualize the test.
@Test | |
void readQRCode() { | |
MobileElement qrCodeElement = driver.findElement(By.id("com.example.qrcode:id/imageView")); | |
File screenshot = driver.getScreenshotAs(OutputType.FILE); | |
String content = decodeQRCode(generateImage(qrCodeElement, screenshot)); | |
assertThat(content).isEqualTo("f3ce8d4d-074f-483f-9fd0-45c7947fd40c"); | |
} |
qrCodeElement
.File
.decodeQRCode
method sending, as parameters, the element and image (screenshot).decodeQRcode
method.Did you notice that the decodeQRCode
uses another method generateImage
? This method uses the element dimensions to crop the QR Code from the screenshot. It’s a better approach to avoid any problem when we are reading it using Zxing.
private BufferedImage generateImage(MobileElement element, File screenshot) { | |
BufferedImage qrCodeImage = null; | |
try { | |
BufferedImage fullImage = ImageIO.read(screenshot); | |
Point imageLocation = element.getLocation(); | |
int qrCodeImageWidth = element.getSize().getWidth(); | |
int qrCodeImageHeight = element.getSize().getHeight(); | |
int pointXPosition = imageLocation.getX(); | |
int pointYPosition = imageLocation.getY(); | |
qrCodeImage = fullImage.getSubimage(pointXPosition, pointYPosition, qrCodeImageWidth, qrCodeImageHeight); | |
ImageIO.write(qrCodeImage, "png", screenshot); | |
} catch (IOException e) { | |
log.error("Problem during the image generation", e); | |
} | |
return qrCodeImage; | |
} |
Now we can use the generateImage
result, which is a BufferedImage
, as a parameter on decodeQRCode
. We are using here the specific Zxing classes to decode the image and return its content.
private static String decodeQRCode(BufferedImage qrCodeImage) { | |
Result result = null; | |
try { | |
LuminanceSource source = new BufferedImageLuminanceSource(qrCodeImage); | |
BinaryBitmap bitmap = new BinaryBitmap(new HybridBinarizer(source)); | |
result = new MultiFormatReader().decode(bitmap); | |
} catch (NotFoundException e) { | |
log.error("QRCode not found", e); | |
} | |
return result.getText(); | |
} |
Now, we can use the UUID returned from the QR Code content to assert it. In real life, it could be a key to validate a transaction.
You can take a look at https://github.com/eliasnogueira/appium-read-qrcode to see a complete example using Appium to interact with the mobile app.
In this Web App solution, we will try to mimic a real-life situation where we need to validate a transaction reading a QR Code content. Usually, it will contain a unique and temporary key to validate the transaction you are doing.
Unlike the Mobile app, we can read the QR Content getting the image from the image src
attribute. After that, we can use Zxing to decode it.
We first need to navigate to the page and get the content of the image src attribute.
@Test | |
void readQRCodeFromURL() { | |
String qrCodeFile = driver.findElement(By.id("qr")).getAttribute("src"); | |
// get the qr code content and assert the result | |
String qrCodeResult = decodeQRCode(qrCodeFile); | |
assertThat(qrCodeResult).isEqualTo("c72a0de5-eba3-4bf0-bde2-fc709e71df29"); | |
} |
decodeQRcode
method to decode itBut Zxing needs an image to read its content. So we need to transform the full image path into an image.
private static String decodeQRCode(Object qrCodeImage) { | |
Result result = null; | |
try { | |
BufferedImage bufferedImage; | |
// if not (probably it is a URL | |
if (((String) qrCodeImage).contains("http")) { | |
bufferedImage = ImageIO.read((new URL((String)qrCodeImage))); | |
// if is a Base64 String | |
} else { | |
byte[] decoded = Base64.decodeBase64((String)qrCodeImage); | |
bufferedImage = ImageIO.read(new ByteArrayInputStream(decoded)); | |
} | |
LuminanceSource source = new BufferedImageLuminanceSource(bufferedImage); | |
BinaryBitmap bitmap = new BinaryBitmap(new HybridBinarizer(source)); | |
result = new MultiFormatReader().decode(bitmap); | |
} catch (NotFoundException | IOException e) { | |
log.error("Error reading the QR Code", e); | |
} | |
return result.getText(); | |
} |
qrCodeImage
content contains the text http
, so we know it is an image path.Back to the test, we are getting the QR code content and asserting it by the expected UUID.
We could have the scenario where, instead of adding the image path in the src attribute, we have an image as a Base64 string. It can happen when it’s generated dynamically.
In the code above, you can see:
else
statementBufferedImage
. Now we can decode it using ZxingYou can take a look at https://github.com/eliasnogueira/selenium-read-qrcode/ to see a complete example using Selenium WebDriver interacting with the web app and get the image content.
Happy tests!