DEV Community

Cover image for Mobilewright Part 2: From The First Test to a Real Test Suite
Stipe
Stipe

Posted on

Mobilewright Part 2: From The First Test to a Real Test Suite

You've written your first Mobilewright test. Now let's grow it into a suite that's organized, readable and ready to run on real devices.

In Part 1 we covered what Mobilewright is, why it feels so familiar if you know Playwright.

One basic test isn't a test suite. What happens when you have 10 tests? 50? Several screens, several devices, and a teammate who wants to add tests too?

In this post I will take that single test and grow it, step by step, into something you would be proud to hand over to your team. As before, there is very little code: this is about how to think about structure, not memorizing syntax.


How your very first test might have looked like

Your first test looked roughly like this:

test('user can log in', async ({ screen }) => {
  await screen.getByTestId('username').fill('demo');
  await screen.getByTestId('password').fill('Bigs3cret');
  await screen.getByRole('button', { name: 'Log in' }).tap();
  await expect(screen.getByText('Welcome')).toBeVisible();
});
Enter fullscreen mode Exit fullscreen mode

It works. But imagine 20 tests that all start by logging in. If the login button gets a new test ID or name. You'd be fixing the same line in 20 places.

That's the first problem we'll solve.


Step 1: Give every screen its own home (Screen Objects)

In web testing with Playwright, you have probably heard of Page Objects. In mobile testing, the same idea is usually called Screen Objects.

The idea is simple:

One class per app screen. It knows where the buttons are and what you can do on that screen.

So instead of tests that talk about test IDs and buttons, you get tests that talk about what the user does:

test('user can log in', async ({ loginScreen, homeScreen }) => {
  await loginScreen.login('demo', 'secret');
  await expect(homeScreen.welcomeMessage).toBeVisible();
});
Enter fullscreen mode Exit fullscreen mode

That reads almost like English. And when the login button changes, you update one file, the Login screen class, and every test is fixed.

Beginner tip: start with a small base class that every screen extends. It's a handy place for things all screens share, such as "am I on Android or iOS?"


Step 2: Let fixtures do the setup for you

Did you notice loginScreen and homeScreen in the test above? We didn't create them with new LoginScreen(...). They just appeared.

That's a fixture, one of Playwright's best features, and Mobilewright has it too.

A fixture is a way of saying:

"Whenever a test asks for loginScreen, build one and hand it over."

You define it once, and every test can simply ask for what it needs. Your tests stay short and focused on behavior, and all the setup lives in one place.

If you've used Playwright's page fixture, you've already used this pattern. You're now just making your own.


Step 3: One config, many devices

Here is where mobile starts to feel different from web, and where Mobilewright shines.

On the web, Playwright projects let you run the same tests on Chrome, Firefox and Safari. In Mobilewright, projects are devices:

  • a Pixel emulator
  • an iPhone simulator
  • a real Samsung phone in the cloud

You list your devices once in the config, and then choose which one to run on:

npx mobilewright test --project="Pixel 8"
Enter fullscreen mode Exit fullscreen mode

A nice pattern I recommend is keeping the device list in a small separate file (JSON works well). Then adding a new device means adding one line, not editing your config logic.


Step 4: Same tests, laptop or cloud

In Part 1 I mentioned that you can start locally and move to real devices later. Here is how that looks in practice.

Your config can check one environment variable:

  • No cloud key set → use the local driver and your emulator
  • Cloud key set → use real devices from a device farm

Your tests don't change at all. Only where they run changes.

This is great for teams: developers run tests on their own emulators during the day, and the pipeline runs the same tests on real phones every night.

Beginner tip: when running locally, make sure that cloud key is empty. Otherwise you might accidentally book a cloud device when you only wanted your emulator.


Step 5: Write tests your whole team can read (BDD)

Once your suite grows, you will have people asking:

"What exactly do our mobile tests check?"

Reading TypeScript isn't fun for everyone. That's where BDD (Behavior-Driven Development) comes in. You describe tests in plain language:

Scenario: User logs in with valid credentials
  Given the app is open on the login screen
  When the user logs in with valid credentials
  Then the home screen is displayed
Enter fullscreen mode Exit fullscreen mode

Each line is connected to a small piece of code (a "step"), and those steps use your Screen Objects from Step 1. Everything you've built so far fits together:

Feature file → Steps → Screen Objects → Mobilewright

Product owners can read the feature files, testers can write new scenarios by reusing existing steps, and the automation runs underneath.


Step 6: Stop preparing data through the app

This is the lesson that saved me the most time.

Say a test needs a brand-new user. The tempting approach is to tap through the sign-up screens at the start of every test. Don't. It's slow, it's flaky, and if sign-up breaks, every test fails, even ones that have nothing to do with sign-up.

Instead:

  • Create test data with API calls before the test (in a "before" hook)
  • Clean up with API calls after the test (in an "after" hook)
  • Use the app's UI only for what you're actually testing

Your tests get faster and more stable, and when something fails you know exactly where the problem is.


Step 7: Every test starts fresh

This one sounds obvious, but it's easy to forget.

If test A logs in and test B silently relies on already being logged in, you have created a hidden dependency. Which is path to flaky test.

Rule of thumb: Every test should start from a clean app, as if it were just installed. Yes, it costs a few seconds per test. It saves hours of debugging failed tests.


Step 8: Learn to read failures

As your suite grows, failures happen. That's normal, and it's the whole point of testing!

Mobilewright gives you good tools here, just like Playwright:

  • The HTML report shows each step, how long it took, and where it failed
  • The UI tree on failure is a snapshot of everything that was on screen when the test broke (switch it on in the config)
  • mobilewright doctor is still your friend whenever something about the environment feels off

A few things that might save you time:

  • A locator found the wrong element. For example, searching for the text "Login" matched the screen title instead of the button. Be specific: prefer test IDs.
  • Mobilewright doesn't complain about duplicates. Playwright on the web fails when a locator matches two elements. Mobile can quietly pick the first one. Another reason to use unique test IDs.
  • A button said it was disabled when it wasn't. Some apps report a button as disabled while the keyboard is open. Check what the user actually sees, not only the button's state.

Step 9: Run it automatically (CI)

Now that your suite is organized, the last step is making it run without you.

Since it is "just" a Node.js project with a test command, you can run it in any CI system, such as GitHub Actions, GitLab CI or others. The typical setup:

  1. Install dependencies
  2. Get the app build (APK or iOS app)
  3. Run the tests against cloud devices (no emulator needed on the CI machine)
  4. Save the HTML report as a build artifact so anyone can open it

Remember Step 4? That's why it matters. The tests that run in CI are the exact same tests you run on your laptop.


What your project looks like now

Let's zoom out. We started with one test file. Now we have:

features/      → plain-language scenarios (BDD)
steps/         → glue between scenarios and screens
screens/       → one class per app screen
fixtures/      → setup that tests can ask for
api/           → test data creation and cleanup
devices.json   → which devices we run on
mobilewright.config.ts
Enter fullscreen mode Exit fullscreen mode

Every folder has one clear job. A new teammate can open the project and understand it in minutes.


Recap

Going from one test to a real suite comes down to a few ideas:

  1. Screen Objects keep locators in one place
  2. Fixtures keep setup out of your tests
  3. Projects = devices, so one test runs anywhere
  4. BDD makes tests readable for the whole team
  5. APIs prepare test data, the UI is only for testing
  6. Every test starts fresh
  7. Reports and UI trees explain failures
  8. CI runs it all for you

And the best part? Almost every one of these ideas comes straight from Playwright. If you learned it for web, you already know it for mobile.


How is your mobile test suite organized? Do you use Screen Objects, BDD, or something else entirely? Tell me in the comments! 👇

Top comments (0)