I have a use case wherein one user logs in to app on mobile and another user logs into application on desktop chrome browser and they chat with each other.
How do i do this simultaneously in run using serenity tests, since driver types are two different ones (one is appium android driver, other is chrome driver)?
You can use the Screenplay pattern to do this - each actor has their own driver.
Thanks. Any example projects you know of in github which i can refer to? Also, does this mean, i need to have multiple serenity.properties files?
this uses two actors with 2 different drivers. they are @Managed here, your use case would most likely mean instantiating the different drivers yourself in your @Before. You don't need multiple property files
For my use case, i need to use 2 different drivers (chrome and android), not 2 drivers of same type. Also, i already have a serenity cucumber appium framework which i am using for android test. This framework uses cucumberwithserenity.class which executes tests from feature files. So, if i have to upgrade this framework to also use chrome driver along with appium parallelly, is it possible with this setup? or are you saying if i have to use screenplay and actors for running multiple different drivers at the same time, it can only be done using serenityrunner.class and not through feature files?
I know i am asking a lot of questions. I am new to serenity, so please bear with my queries.
i am not very acquainted with the cucumber integrations. i know cucumber is able to do screenplay so it should be possible to do what you want. How to set up actors that way is something https://gitter.im/serenity-bdd/serenity-core or @wakaleo could answer better.
Thank you. i actually found a framework with serenity-cucumber integration (https://github.com/serenity-bdd/serenity-cucumber4-starter/tree/screenplay) with actor centric logic wherein actors can be driven from feature files. Although this automatically initiates multi drivers based on number of actors. it still does so with same type of driver. @wakaleo is it possible to configure this framework wherein i have 2 actors with one actor being appium-android driver and another being web chrome driver?
good find, what you may want to do is to replace the OnlineCast on https://github.com/serenity-bdd/serenity-cucumber4-starter/blob/screenplay/src/test/java/starter/stepdefinitions/SearchOnDuckDuckGoStepDefinitions.java#L24 with
desktop = new Actor("desktop")
desktop.can(BrowseTheWeb.with(your_desktop_webdriver));
mobile = new Actor("mobile")
mobile.can(BrowseTheWeb.with(your_appium_webdriver));
I'm currently working on a solution like this. It's a chat app where some users are on mobile, while others are on desktop. I ended up making my own Cast that creates the relevant driver based on the actor name.
public class MyCast extends Cast {
@Override
public Actor actorNamed(String actorName, Ability... abilities) {
return super.actorNamed(actorName, BrowseTheWeb.with(theDriverFor(actorName)));
}
private WebDriver theDriverFor(String actorName) {
EnvironmentVariables environmentVariables = Injectors.getInjector().getProvider(EnvironmentVariables.class).get();
WebDriver driver = null;
if(actorName.contains("customer")) {
driver = MyCustomConfig.from(environmentVariables).getDriverForRole(Role.CUSTOMER);
}
else if(actorName.contains("receptionist")) {
driver = MyCustomConfig.from(environmentVariables).getDriverForRole(Role.RECEPTIONIST);
}
else if(actorName.contains("sales")) {
driver = MyCustomConfig.from(environmentVariables).getDriverForRole(Role.SALES);
}
return driver;
}
MyCustomConfig is where the magic happens. Essentially I have a list of device configs it reads from serenity.conf. At runtime it will move these configs to the appropriate place that serenity expects them to be.
public class MyCustomConfig {
private final EnvironmentVariables environmentVariables;
private final String GLOBAL_DEVICES = "myconfig.devices.all.";
private MyCustomConfig(EnvironmentVariables environmentVariables) {
this.environmentVariables = environmentVariables;
}
public static MyCustomConfig from(EnvironmentVariables environmentVariables) {
return new MyCustomConfig(environmentVariables);
}
public WebDriver getDriverForRole(Role role) {
String userDeviceKey = "myconfig." + role.toString().toLowerCase() + ".device";
String device = environmentVariables.getProperty(userDeviceKey);
return getDriverForDevice(device);
}
public WebDriver getDriverForDevice(String deviceName) {
String deviceKey = "myconfig.devices." + deviceName + ".";
Map<String, String> deviceProperties = getDeviceProperties(deviceKey);
WebdriverManager webDriverManager = ThucydidesWebDriverSupport.getWebdriverManager();
for(String prop : deviceProperties.keySet()) {
webDriverManager.withProperty(prop, deviceProperties.get(prop));
}
String driverName = deviceProperties.get("webdriver.remote.driver");
return webDriverManager.getWebdriver(driverName);
}
@SuppressWarnings({"unchecked", "rawtypes"})
private Map<String, String> getDeviceProperties(String key) {
Properties globalDeviceProps = environmentVariables.getPropertiesWithPrefix(GLOBAL_DEVICES);
Properties deviceSpecificProps = environmentVariables.getPropertiesWithPrefix(key);
deviceSpecificProps.putAll(globalDeviceProps);
renameKeys(deviceSpecificProps, key);
return (Map)deviceSpecificProps;
}
/**
* Strips myconfig prefix from properties. e.g. 'myconfig.devices.pixel3.browserstack' will become 'browserstack'
* @param props device properties
* @param deviceKey a key pointing to device specific props e.g. myconfig.devices.pixel3
*/
private void renameKeys(Properties props, String deviceKey) {
for(String prop : props.stringPropertyNames()) {
String value = props.getProperty(prop);
props.remove(prop);
prop = prop.replace(GLOBAL_DEVICES, "")
.replace(deviceKey, "");
props.put(prop, value);
}
}
This is what my config file looks like. The devices list contains all the configs I need, I'm using BrowserStack. Yours may look different. MyCustomConfig strips myconfig prefix from properties. e.g. 'myconfig.devices.pixel3.browserstack' will become 'browserstack'
myconfig{
customer.device = pixel3
receptionist.device = win10chrome
sales.device = iphone8
devices {
pixel3{
webdriver.remote.driver = appium
browserstack {
osVersion = "10.0"
deviceName = "Google Pixel 3"
}
appium {
platformName = Android
browserName = chrome
}
}
win10chrome{
webdriver.remote.driver = chrome
browserstack {
os = "Windows"
osVersion = "10"
}
}
all{
browserstack {
realMobile = true
projectName = "My Project"
buildName = "Debugging"
debug = true
}
appium {
hub = "REDACTED"
}
}
}
}
My only complaint about this implementation is that it doesn't seem to work well with SerenityRunner (JUnit) . But it works fine with CucumberSerenityRunner
good find, what you may want to do is to replace the
OnlineCaston https://github.com/serenity-bdd/serenity-cucumber4-starter/blob/screenplay/src/test/java/starter/stepdefinitions/SearchOnDuckDuckGoStepDefinitions.java#L24 withdesktop = new Actor("desktop") desktop.can(BrowseTheWeb.with(your_desktop_webdriver)); mobile = new Actor("mobile") mobile.can(BrowseTheWeb.with(your_appium_webdriver));
You mean like this?
public void setTheStage() {
Actor WebUser = new Actor("WebUser");
WebUser.can(BrowseTheWeb.with(webdriver));
Actor MobUser = new Actor("MobUser");
MobUser.can(BrowseTheWeb.with(mobdriver));
}
looks about right
@globalworming its not working. when i run it, the functions anActorIsOnStage() and withCurrentActor(), part of OnStage class retruns null pointer exception. I guess this is because OnStage.setTheStage hasnt happened in my setTheStage() function of scenariosteps class file. Not sure how to pass the actors directly in place of "new OnlineCast()" inside the OnStage.setTheStage
I'm currently working on a solution like this. It's a chat app where some users are on mobile, while others are on desktop. I ended up making my own Cast that creates the relevant driver based on the actor name.
public class MyCast extends Cast { @Override public Actor actorNamed(String actorName, Ability... abilities) { return super.actorNamed(actorName, BrowseTheWeb.with(theDriverFor(actorName))); } private WebDriver theDriverFor(String actorName) { EnvironmentVariables environmentVariables = Injectors.getInjector().getProvider(EnvironmentVariables.class).get(); WebDriver driver = null; if(actorName.contains("customer")) { driver = MyCustomConfig.from(environmentVariables).getDriverForRole(Role.CUSTOMER); } else if(actorName.contains("receptionist")) { driver = MyCustomConfig.from(environmentVariables).getDriverForRole(Role.RECEPTIONIST); } else if(actorName.contains("sales")) { driver = MyCustomConfig.from(environmentVariables).getDriverForRole(Role.SALES); } return driver; }MyCustomConfig is where the magic happens. Essentially I have a list of device configs it reads from serenity.conf. At runtime it will move these configs to the appropriate place that serenity expects them to be.
public class MyCustomConfig { private final EnvironmentVariables environmentVariables; private final String GLOBAL_DEVICES = "myconfig.devices.all."; private MyCustomConfig(EnvironmentVariables environmentVariables) { this.environmentVariables = environmentVariables; } public static MyCustomConfig from(EnvironmentVariables environmentVariables) { return new MyCustomConfig(environmentVariables); } public WebDriver getDriverForRole(Role role) { String userDeviceKey = "myconfig." + role.toString().toLowerCase() + ".device"; String device = environmentVariables.getProperty(userDeviceKey); return getDriverForDevice(device); } public WebDriver getDriverForDevice(String deviceName) { String deviceKey = "myconfig.devices." + deviceName + "."; Map<String, String> deviceProperties = getDeviceProperties(deviceKey); WebdriverManager webDriverManager = ThucydidesWebDriverSupport.getWebdriverManager(); for(String prop : deviceProperties.keySet()) { webDriverManager.withProperty(prop, deviceProperties.get(prop)); } String driverName = deviceProperties.get("webdriver.remote.driver"); return webDriverManager.getWebdriver(driverName); } @SuppressWarnings({"unchecked", "rawtypes"}) private Map<String, String> getDeviceProperties(String key) { Properties globalDeviceProps = environmentVariables.getPropertiesWithPrefix(GLOBAL_DEVICES); Properties deviceSpecificProps = environmentVariables.getPropertiesWithPrefix(key); deviceSpecificProps.putAll(globalDeviceProps); renameKeys(deviceSpecificProps, key); return (Map)deviceSpecificProps; } /** * Strips myconfig prefix from properties. e.g. 'myconfig.devices.pixel3.browserstack' will become 'browserstack' * @param props device properties * @param deviceKey a key pointing to device specific props e.g. myconfig.devices.pixel3 */ private void renameKeys(Properties props, String deviceKey) { for(String prop : props.stringPropertyNames()) { String value = props.getProperty(prop); props.remove(prop); prop = prop.replace(GLOBAL_DEVICES, "") .replace(deviceKey, ""); props.put(prop, value); } }This is what my config file looks like. The devices list contains all the configs I need, I'm using BrowserStack. Yours may look different.
MyCustomConfigstrips myconfig prefix from properties. e.g. 'myconfig.devices.pixel3.browserstack' will become 'browserstack'myconfig{ customer.device = pixel3 receptionist.device = win10chrome sales.device = iphone8 devices { pixel3{ webdriver.remote.driver = appium browserstack { osVersion = "10.0" deviceName = "Google Pixel 3" } appium { platformName = Android browserName = chrome } } win10chrome{ webdriver.remote.driver = chrome browserstack { os = "Windows" osVersion = "10" } } all{ browserstack { realMobile = true projectName = "My Project" buildName = "Debugging" debug = true } appium { hub = "REDACTED" } } } }My only complaint about this implementation is that it doesn't seem to work well with
SerenityRunner(JUnit) . But it works fine withCucumberSerenityRunner
I will also try something like this. Can you show me ur scenariosteps class file where you are calling ur custom MyCast to setup the drivers based on actors?
@Murali6205 my steps look like this:
@Before
public void setTheStage() {
OnStage.setTheStage(new MyCast());
}
@Given("{actor} has done a thing")
public void hasDoneAThing(Actor actor) {
actor.attemptsTo(... );
}
I also have a custom cucumber parameter type to easily get my actors
@ParameterType(".*")
public Actor actor(String actorName) {
return OnStage.theActorCalled(actorName);
}
My feature file would look like the following
note the 'customer' will be passed in as a parameter and use the configured driver when theActorCalled is called
Scenario: Do something
Given customer has done a thing
...
@thePantz This worked wonderfully. Thank you very much
this sound really useful. could you maybe create a minimal example of this @thePantz and publish it as git repo?
Hi @globalworming I haven't had time to make a repo for this but I plan to. I will update you when it's available
@globalworming here's a sample, as requested: https://github.com/thePantz/serenitybdd-cucumber-multidevice