How to write a single Katalon script for both desktop and mobile browsers without branching logic?

Hello everyone,

I’m automating functional tests for a web platform that users can access either from desktop computers (using various browsers) or from mobile devices (also on different browsers).

My goal is to write a single Katalon test script (using WebUI keywords) targeting the desktop site and then run that exact same script against the mobile site—without maintaining separate copies or adding any mobile-specific conditionals. This approach would allow me to:

Verify feature parity between the desktop and mobile web versions in one automation suite

Reduce maintenance overhead by avoiding duplicate scenarios

Ensure consistency of assertions and test data across both screen sizes

So far, I’ve tried:

Switching the browser type (Chrome → ChromeMobile) via TestOps environments

Adjusting Desired Capabilities (e.g., deviceName, platformName, browserName) in separate profiles

Parameterizing viewport dimensions in the Profile settings

However, I still end up wrapping a lot of logic in if (isMobile) { … } else { … } blocks, which defeats the purpose of having a unified script.

My questions are:

Is there a recommended Katalon approach for creating a single script that works on both desktop and mobile browsers without introducing different logic branches?

How are you handling these kinds of scenarios in your own projects?

Are there any code samples or project templates that illustrate this pattern?

Thank you in advance for any guidance, examples, or best practices you can share!
I look forward to your insights.


Camilo (QA Automation)

1 Like

I think it would depend if a Windows TestObject is different than a Mobile TestObject. If they are different object types, then you could use Keywords for all your if/else codes but using overloading of your Keyword methods. Yes, you would need two methods for each if/else, but your test would not know/care which method it was using. Your test would just call the appropriate method, without any if/else, and “Groovy” would figure out which object to use. You could put one method for a Mobile object and another for a Windows object.

Unfortuately, if both of them are just a “TestObject”, then poof–this doesn’t work. Try it on one method to see if it works and get back to us.

	/*
	 * Review the status using Windows
	 */
	@Keyword
	def reviewStatus(WindowsTestObject wobj) {
		if (!WIndows.verifyElementVisible(wobj)) {
			WebUI.comment("Status is not visible!")
		}
	}

	/*
	 * Review the status using Mobiles
	 */
	@Keyword
	def reviewStatus(TestObject mobj) {
		if (!Mobile.verifyElementVisible(mobj)) {
			WebUI.comment("Status is not visible!")
		}
	}

Note: you would have to have both methods in the same class.

Note2: You could also code like:

(isMobile) ? Mobile.click(...) : Windows.click(...);

I guess, @camilo.silva1 have a project that uses only WebUI.xxxx keywords. He runs it against 2 types of devices: Chrome browser on a PC and on a Mobile device.

I guess, @camilo.silva1’s project uses no Windows.xxxx keywords, no Mobile.xxxx keywords. @grylion54 misunderstood @camilo.silva1

Is it possible to share the project’s source to us somehow?

If we could read the source, we might be able to understand your issue better.

if your AUT is responsise, try opening the browsers in different view ports(Small -mobile , medium- tab, large-desktop) , pass this parameter while executing your tests

Hi Kazurayam, thanks for the interest in the problem, i can give a fragment of the script, actually have two scripts one for mobile device and one for desktop (PC) but the idea is only one script for both where the object is the same for the dektop and mobile device, gonna send the fragment for the two scripts.

Desktop script:

import static com.kms.katalon.core.checkpoint.CheckpointFactory.findCheckpoint
import static com.kms.katalon.core.testcase.TestCaseFactory.findTestCase
import static com.kms.katalon.core.testdata.TestDataFactory.findTestData
import static com.kms.katalon.core.testobject.ObjectRepository.findTestObject
import static com.kms.katalon.core.testobject.ObjectRepository.findWindowsObject
import com.kms.katalon.core.checkpoint.Checkpoint as Checkpoint
import com.kms.katalon.core.cucumber.keyword.CucumberBuiltinKeywords as CucumberKW
import com.kms.katalon.core.mobile.keyword.MobileBuiltInKeywords as Mobile
import com.kms.katalon.core.model.FailureHandling as FailureHandling
import com.kms.katalon.core.testcase.TestCase as TestCase
import com.kms.katalon.core.testdata.TestData as TestData
import com.kms.katalon.core.testng.keyword.TestNGBuiltinKeywords as TestNGKW
import com.kms.katalon.core.testobject.TestObject as TestObject
import com.kms.katalon.core.webservice.keyword.WSBuiltInKeywords as WS
import com.kms.katalon.core.webui.keyword.WebUiBuiltInKeywords as WebUI
import com.kms.katalon.core.windows.keyword.WindowsBuiltinKeywords as Windows
import internal.GlobalVariable as GlobalVariable
import org.openqa.selenium.Keys as Keys

WebUI.openBrowser(URL)

//WebUI.navigateToUrl(URL)
/*Mobile.startExistingApplication(‘org.mozilla.firefox’)

Mobile.setText(findTestObject(‘Movil_Firefox/android.widget.TextView - Buscar o ingresar direccin’), ‘www.google.com’, 0,
FailureHandling.OPTIONAL)

Mobile.setText(findTestObject(‘Movil_Firefox/txt - Buscar o ingresar direccion_2’), URL, 0)
*/
WebUI.delay(10)

WebUI.click(findTestObject(‘Object Repository/Ecommerce/YaEresCliente/Page_Planes celular Contrata o porta en minutos/button_Ya soy cliente’))

//Mobile.tap(findTestObject(‘Ecommerce/YaEresCliente/Page_Planes celular Contrata o porta en minutos/button_Ya soy cliente’),
// 0)

WebUI.click(findTestObject(‘Object Repository/Ecommerce/YaEresCliente/Page_Exclusivo clientes Ahorra sumando lne_a6b2a8/button_INICIAR SESIN’))

WebUI.click(findTestObject(‘Object Repository/Ecommerce/YaEresCliente/Page_Exclusivo clientes Ahorra sumando lne_a6b2a8/img_SOLICITAR CDIGO POR SMS_closeButton’))

WebUI.closeBrowser()

===the commented step is for the problem about click and tap.===

Mobile script:

import static com.kms.katalon.core.checkpoint.CheckpointFactory.findCheckpoint
import static com.kms.katalon.core.testcase.TestCaseFactory.findTestCase
import static com.kms.katalon.core.testdata.TestDataFactory.findTestData
import static com.kms.katalon.core.testobject.ObjectRepository.findTestObject
import static com.kms.katalon.core.testobject.ObjectRepository.findWindowsObject
import com.kms.katalon.core.checkpoint.Checkpoint as Checkpoint
import com.kms.katalon.core.cucumber.keyword.CucumberBuiltinKeywords as CucumberKW
import com.kms.katalon.core.mobile.keyword.MobileBuiltInKeywords as Mobile
import com.kms.katalon.core.model.FailureHandling as FailureHandling
import com.kms.katalon.core.testcase.TestCase as TestCase
import com.kms.katalon.core.testdata.TestData as TestData
import com.kms.katalon.core.testng.keyword.TestNGBuiltinKeywords as TestNGKW
import com.kms.katalon.core.testobject.TestObject as TestObject
import com.kms.katalon.core.webservice.keyword.WSBuiltInKeywords as WS
import com.kms.katalon.core.webui.keyword.WebUiBuiltInKeywords as WebUI
import com.kms.katalon.core.windows.keyword.WindowsBuiltinKeywords as Windows
import internal.GlobalVariable as GlobalVariable
import org.openqa.selenium.Keys as Keys
Mobile.startExistingApplication(‘org.mozilla.firefox’)

Mobile.setText(findTestObject(‘Movil_Firefox/android.widget.TextView - Buscar o ingresar direccin’), ‘www.google.com’, 0,
FailureHandling.OPTIONAL)

Mobile.setText(findTestObject(‘Movil_Firefox/txt - Buscar o ingresar direccion_2’), ‘URL’, 0)

WebUI.delay(10)

Mobile.tap(findTestObject(‘Ecommerce/YaEresCliente/Page_Planes celular Contrata o porta en minutos/button_Ya soy cliente’),
0)

Mobile.scrollToText(‘Mejora’, FailureHandling.OPTIONAL)

Mobile.tap(findTestObject(‘Object Repository/Ecommerce/YaEresCliente/Page_Exclusivo clientes Ahorra sumando lne_a6b2a8/button_INICIAR SESIN’),
0)

Mobile.verifyElementVisible(findTestObject(‘Ecommerce/YaEresCliente/Page_Exclusivo clientes Ahorra sumando lne_a6b2a8/android.widget.Image - close icon’),
5)

Mobile.tap(findTestObject(‘Ecommerce/YaEresCliente/Page_Exclusivo clientes Ahorra sumando lne_a6b2a8/android.widget.Image - close icon’),
0)

WebUI.delay(10)

WebUI.closeBrowser()

Hope this help you to understand the problem, gonna be attentive for the reply

I made a demo project just to give you an idea.

Test Cases/responsive

import static com.kms.katalon.core.testcase.TestCaseFactory.findTestCase

import com.kms.katalon.core.webui.keyword.WebUiBuiltInKeywords as WebUI

WebUI.openBrowser('')
WebUI.maximizeWindow()
WebUI.callTestCase(findTestCase("shared/duckduckgo.com"), [:])
WebUI.closeBrowser()

This script opens a browser with the maximum width and height of my display.

Test Cases/iPad_768x1024

import static com.kms.katalon.core.testcase.TestCaseFactory.findTestCase

import com.kms.katalon.core.webui.keyword.WebUiBuiltInKeywords as WebUI

WebUI.openBrowser('')
WebUI.setViewPortSize(768, 1024)
WebUI.callTestCase(findTestCase("shared/duckduckgo.com"), [:])
WebUI.closeBrowser()

This script opens a browser with middle size like iPad

Test Cases/iPhone 14 Pro Max_430_932

import static com.kms.katalon.core.testcase.TestCaseFactory.findTestCase

import com.kms.katalon.core.webui.keyword.WebUiBuiltInKeywords as WebUI

WebUI.openBrowser('')
WebUI.setViewPortSize(430, 932)
WebUI.callTestCase(findTestCase("shared/duckduckgo.com"), [:])
WebUI.closeBrowser()

This script opens a browser of small window size like iPhone

Test Cases/shared/duckduckgo.com

import com.kms.katalon.core.testobject.ConditionType
import com.kms.katalon.core.testobject.TestObject
import com.kms.katalon.core.webui.keyword.WebUiBuiltInKeywords as WebUI

TestObject makeTestObject(String id, String xpath) {
	TestObject tObj = new TestObject(id)
	tObj.addProperty("xpath", ConditionType.EQUALS, xpath)
	return tObj
}

WebUI.navigateToUrl("https://duckduckgo.com/")
TestObject inputField = makeTestObject("input", "//input[@id='searchbox_input']")

WebUI.verifyElementPresent(inputField, 10)
WebUI.setText(inputField, "katalon")

TestObject button = makeTestObject("button", "//button[@aria-label='Search']")
WebUI.waitForElementPresent(button, 10)
WebUI.click(button)

TestObject anchor = makeTestObject("anchor", "//a[contains(@href, 'https://katalon.com/')]")

WebUI.verifyElementPresent(anchor, 10)

WebUI.delay(3)

The source is published at kazurayam (kazurayam) / Repositories · GitHub

@camilo.silva1

Let me ask you a question.

Do you want to test a set of web pages (HTML+CSS+JS) generated by a responsive web application (e.g, https://www.google.com)?

Or, do you want to test the a browser application (e.g, Firefox) itself to make sure it works fine on both of PC device and Android device?

The code you shared above gave me an impression that you just want to test a web application with different viewport sizes. Then you do not need to call Mobile.* keywords at all. If the target web app applies the Responsive Web Design, only the width of the viewport matters.

If you have a good reason to call Mobile.* keyword in your unified test, please describe the reason more clearly.

@kazurayam
Thank you very much for your response. The purpose, more than testing the responsiveness of the web design, is to perform executions in a “Mobile browser” environment to test the same functionality that was tested in a “Desktop Browser”. When executing a script based on “Web.UI” keywords in the “Mobile Browser” environment, we encountered issues (as mentioned in the previous response). This led to the decision to use “Mobile.*” keywords, but this would imply a completely separate development effort. This is how the question arose about creating a single script that can be executed in both environments. The mobile browsers used for testing were Chrome and Firefox (where Firefox Mobile did not recognize click(), and we had to use the tap() command instead)

How do you implement the flag isMobile in your current test script?

Is the isMobile flag set in an Execution Profile?

@camilo.silva1

Possibly you want to invent a new set of custom Keywords that wraps WebUI.* and Mobile.*, such as Silva1.*. For example, Silva1.clickOrTap(TestObject) will call WebUI.click(TestObject) if isMobile is found false, will call Mobile.tap(TestObject) if isMobile is found true. And your Test Case script will call Silva1.clickOrTap(TestObject) rather than WebUI.click(TestObject) and Mobile.tap(TestObject). Thus your Test Case script will be shortened.

I don’t know if this idea really works or not. I don’t know how much feasible to invent Silva1.* keywords. Just try and see.

Then, you just need a test that calls Mobile.* only.
You should drop WebUI.*-based test cases.
Your task will be simplified to the half.

@camilo.silva1

Yout might be able develop a new tool that reads a WebUI.*-based Test Case script, transform it, generate a Mobile.*-based script. And vice versa. Generative programming!

You can utilize the AST Transformations of Groovy language to implement it:

Katalon Studio bundles several Groovy classes in the package com.kms.katalon.core.ast that helps working on the Abstract Syntax Tree (AST) Transformation; for example

Those classes implement serialization processing of AST object into Groovy source code. If we are to develop the new tool “Test Cases Trasformer” from WebUI.* to Mobile.*, those buitl-in classes would help.

I have a web application that we run on both desktop and mobile browsers using BrowserStack. The test code does not use if-else branching. Instead, I’ve created a separate object repository for mobile-specific elements—those that cannot be located using desktop objects.

The code is designed to dynamically pick objects from the mobile repository when needed. For example:

I have created objectRepo for each mobile devices and separate folder for desktops.
Created a global variable objectRepoPath which is set based on the device I am running test.
Also created a global variable browserStackMobileTest which is set to true if test is for mobile else by default false

By default path is set to use desktop repo folder

For mobile test set
GlobalVariable.browserStackMobileTest = true
Then set path GlobalVariable.objectRepoPath = “Mobile/iPad”
or
GlobalVariable.objectRepoPath = “Mobile/iPhone” etc

In the code where you are referring that object ,use the global variable for object repo to pick up correct platform dependent object from respective directory

WebUI.scrollToElement(findTestObject("${GlobalVariable.objectRepoPath}/.../objectName"), 10)

If an object is not found using the desktop path, the code falls back to a dynamic path that points to the mobile object repository. This approach ensures that mobile-specific objects are used when necessary, without adding conditional logic.

Additionally, I’ve modified the script to handle scrolling on mobile devices and replaced certain keywords—for instance, using sendKeys instead of setText—to improve compatibility across both platforms.

2 Likes