Sometimes there is a need to test the way two different apps interact or work together.
Say you have a mobile app for a coffee shop. When you place your order with the app, a notice shows up on the order terminal for the barista. They can then fulfill the order and have it ready for you — no waiting in line.
As a customer I want to place an order for coffee on my iPhone So that it's ready when I get to the coffee shop
But if they can’t fulfill the order (maybe they’re out of caramel-soy-macchi-whatever mix) they can let the customer know so that they can cancel or place a new order without waiting to find out that their order is not available.
As a barista I want to notify customers when their order can't be fulfilled So that they can change or cancel their order
There are two different apps here, and two different actors. This can make the test challenging. The obvious solution is to automate both apps at the same time (the mobile app for the customer and the web-based point of sale terminal for the barista.
Your test might looks something like this:
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
public void testCustomerDeviceAndBaristaTerminalTogether() | |
{ | |
// Create two sessions in one test — one on a browser for the barista and one on a mobile device for the customer | |
browser = new RemoteWebDriver(seleniumServerURL, browserCapabilites); | |
phone = new AppiumDriver(appiumServerURL, deviceCapabilities); | |
// Barista logs into terminal app in browser | |
browser.get(baristaApp.loginPage.url); | |
browser.findElement(baristaApp.loginPage.usernameField).sendKeys(barista.username); | |
browser.findElement(baristaApp.loginPage.passwordField).sendKeys(barista.password); | |
browser.findElement(baristaApp.loginPage.loginButton).click(); | |
// Customer logs into mobile app on mobile device | |
phone.findElement(mobileApp.loginScreen.usernameField).sendKeys(customer.username); | |
phone.findElement(mobileApp.loginScreen.passwordField).sendKeys(customer.password); | |
phone.findElement(mobileApp.loginScreen.loginButton).click(); | |
// Customer selects a coffee — after login succeeded | |
coffeeButton = phoneWait.until(ExpectedConditions.elementToBeClickable(mobileApp.menuScreen.coffeeButton)); | |
coffeeButton.click(); | |
// Barista receives the order and marks it out of stock | |
browserWait.until(ExpectedConditions.visibilityOfElementLocated(baristaApp.orderDialog)); | |
browser.findElement(baristaApp.orderDialog.outOfStockButton).click(); | |
// Customer is prompted to cancel or place new order | |
orderConfirmationDialog = phoneWait.until(ExpectedConditions.visibilityOfElementLocated(mobileApp.orderConfirmationDialog)); | |
// User receives out of stock notification and can cancel or place new order | |
assertThat(orderConfirmationDialog.getText()).contains("Out of Stock"); | |
assertThat(orderConfirmationDialog.findElement(By.accessibilityId("Cancel Order"))).exists(); | |
assertThat(orderConfirmationDialog.findElement(By.accessibilityId("Place New Order"))).exists(); | |
} |
The problem here is that coordination between the two apps can be tricky. Synchronization and timing isn’t guaranteed and I’m not sure if the explicit waits will always handle this.
Also, it requires standing up both environments and making sure that the mobile app can communicate with your web app. It can get tricky. Not to mention it will be inherently slower and the odds of random failures increases.
Another thing you can do is test the majority of use cases independently. This is hinted at by our two stories above. One for the barista (web app) and a separate one for the customer (mobile app.)
Unless you have a really unique architecture, it’s likely that the two apps don’t actually know anything about each other. They probably communicate through web services with a shared back end database or message queue.
Really, what you want to do is test each app independently and how it interacts with the service. The service can be mocked or stubbed for some use cases, but for end-to-end tests, it makes sense to use the service.
So your test will now look something like this:
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
public void testCustomerDeviceWithBaristaApi() | |
{ | |
// Create a session for the customer app | |
phone = new AppiumDriver(sauceURL, deviceCapabilities); | |
// Create an API service that interacts with the mobile app | |
baristaAPI = new BaristaAPI(barristUsername, baristaPassword); | |
// Customer logs into mobile app on mobile device | |
phone.findElement(mobileApp.loginScreen.usernameField).sendKeys(customer.username); | |
phone.findElement(mobileApp.loginScreen.passwordField).sendKeys(customer.password); | |
phone.findElement(mobileApp.loginScreen.loginButton).click(); | |
// Customer selects a coffee — after login succeeded | |
coffeeButton = phoneWait.until(ExpectedConditions.elementToBeClickable(mobileApp.menuScreen.coffeeButton)); | |
coffeeButton.click(); | |
// API finds the customer order and sets it to out of stock | |
order = baristaApi.findOrdersForCustomer(customer.username).get(0); | |
baristaApi.markOrderAsOutOfStock(order); | |
// Customer is prompted to cancel or place new order | |
orderConfirmationDialog = phoneWait.until(ExpectedConditions.visibilityOfElementLocated(mobileApp.orderConfirmationDialog)); | |
// User receives out of stock notification and can cancel or place new order | |
assertThat(orderConfirmationDialog.getText()).contains("Out of Stock"); | |
assertThat(orderConfirmationDialog.findElement(By.accessibilityId("Cancel Order"))).exists(); | |
assertThat(orderConfirmationDialog.findElement(By.accessibilityId("Place New Order"))).exists(); | |
} |
This requires a clear API and may require some backend interaction that is not normally exposed. But the test is much cleaner (and reliable) and if exposed services require additional security you can have a separate test API endpoint or authorization token that enables the additional functionality. In this case, that shouldn’t be necessary.
You may still want to perform a few end-to-end sanity tests to make sure the environments are communicating correctly and are compatible, but the number of these tests can be greatly reduced — and the speed and reliability of your test suite improved.