Q22 of 26 · Mobile QA
How do you design and implement a screen object model for a mobile Appium framework?
Short answer
Short answer: Mirror the Page Object Model — each app screen is a class encapsulating its locators and user-facing actions. Action methods return screen objects (fluent interface) to enable chaining. A base class holds the driver reference and shared wait utilities.
Detail
The screen object model for mobile has the same benefits as POM for web — locators are centralised, tests read like user journeys, and locator changes require one edit. The mobile-specific considerations:
Base class responsibilities: driver reference, platform getter (iOS | Android), waitForScreen() which waits for the unique element that identifies this screen, shared scroll/swipe helpers.
Screen class structure:
class ProductScreen extends BaseScreen {
private get addToCartBtn() {
return this.platform === 'iOS'
? $('~Add to cart')
: $('~add-to-cart-button');
}
async addToCart(): Promise<CartScreen> {
await this.addToCartBtn.waitForDisplayed();
await this.addToCartBtn.click();
return new CartScreen(this.driver);
}
}
Factory for session setup: a session factory reads the platform from environment config and creates the correct driver with the correct capabilities. Tests import from the factory, never create drivers directly.
Shared components: navigation bars, tab bars, and modal sheets appear across screens. Model them as separate component objects imported by the screens that use them — avoids duplicating locators for the bottom tab bar across every screen class.
What NOT to put in screen objects: assertions. Screen objects expose the state; tests do the asserting. Mixing them couples test intent with implementation — if the assertion belongs in the test, the test should own it.
// EXAMPLE
LoginScreen.ts
import { BaseScreen } from './BaseScreen';
import { HomeScreen } from './HomeScreen';
export class LoginScreen extends BaseScreen {
private get emailField() { return $('~email-input'); }
private get passwordField() { return $('~password-input'); }
private get signInButton() { return $('~sign-in-button'); }
async signIn(email: string, password: string): Promise<HomeScreen> {
await this.emailField.setValue(email);
await this.passwordField.setValue(password);
await this.signInButton.click();
return new HomeScreen(this.driver);
}
}