Modify remote driver URL at runtime - selenium

I have a project which is based on the serenity-bdd/serenity-cucumber-starter project. I'm using test-containers to start a couple of Docker containers as well as a Selenium Grid container to run the test against.
new GenericContainer<>(SELENIUM_IMAGE)
...
.withExposedPorts(SELENIUM_CONTAINER_PORT, SELENIUM_CONTAINER_NOVNC_PORT)
...
);
When the tests start, test-containers will ramp up the containers and bind random host ports to all exposed ports of the containers.
Because of that, I cannot define a fixed value in serenity.conf for the url of the remote driver
webdriver.remote.url = "http://localhost:????/wd/hub"
Thus I need a way to set webdriver.remote.url programmatically.
One option would be to use the FixedHostPortGenericContainer, which allows you define the host port on which the container exposed port will be bound to.
I'd rather would like to use a different approach though, as the developers state that
While this works, we strongly advise against using fixed ports, since this will automatically lead to integrated tests (which are an anti pattern).
So the question is: How can I modify the value of webdriver.remote.url at runtime? Is there any option provided by serenity-bdd to reload the net.thucydides.core.util.SystemEnvironmentVariables at runtime?

Faced recently the same issue, but was lucky enough to find a solution:
import java.net.MalformedURLException;
import java.net.URL;
import org.openqa.selenium.WebDriver;
import org.openqa.selenium.remote.RemoteWebDriver;
import net.serenitybdd.core.webdriver.driverproviders.FirefoxDriverCapabilities;
import net.thucydides.core.guice.Injectors;
import net.thucydides.core.util.EnvironmentVariables;
import net.thucydides.core.webdriver.DriverSource;
public class CustomWebDriverFactory implements DriverSource {
#Override
public WebDriver newDriver() {
try {
String ip = "your_dynamic_ip";
return new RemoteWebDriver(
new URL("http://" + ip + ":4444/wd/hub"),
new FirefoxDriverCapabilities(Injectors.getInjector().getProvider(EnvironmentVariables.class).get()).getCapabilities());
}
catch (MalformedURLException e) {
throw new RuntimeException(e);
}
}
#Override
public boolean takesScreenshots() {
return true;
}
}
So you have to add such factory implementation and define in serenity.properties:
webdriver.driver = provided
webdriver.provided.type = mydriver
webdriver.provided.mydriver = <your_factory_package>.CustomWebDriverFactory
thucydides.driver.capabilities = mydriver

Related

How to start a #Bean with custom parameters after an event had happened with spring for tests?

I am working on adding RepositoryTests with TestContainers framework for a project that uses R2dbc and I am running into the following situation:
1 - On the main project I set r2dbc url (with port and hostname) on application.yaml file and spring data manages everything and things just work.
2 - On the Tests however, I am using TestContainers framework more specifically DockerComposeContainer which I use to create a mocked container using docker-compose.test.yaml file with the databases I need.
3 - This container creates a port number on the go I define a port number on my docker-compose file but the port number that DockerComposeContainer will provide me is random and changes everytime I run the tests, what makes having a static url on application-test.yaml not an option anymore.
So I need to dinamically create this bean R2dbcEntityTemplate at run time and only after the DockerComposeContainer will give me the port number. So my application can connect to the correct port and things should work as expected.
I tried to create this class:
package com.wayfair.samworkgroupsservice.adapter
import io.r2dbc.mssql.MssqlConnectionConfiguration
import io.r2dbc.mssql.MssqlConnectionFactory
import org.springframework.beans.factory.annotation.Autowired
import org.springframework.beans.factory.config.ConstructorArgumentValues
import org.springframework.beans.factory.support.BeanDefinitionRegistry
import org.springframework.beans.factory.support.GenericBeanDefinition
import org.springframework.context.ApplicationContext
import org.springframework.context.annotation.Profile
import org.springframework.data.r2dbc.core.DefaultReactiveDataAccessStrategy
import org.springframework.data.r2dbc.core.R2dbcEntityTemplate
import org.springframework.data.r2dbc.dialect.SqlServerDialect
import org.springframework.r2dbc.core.DatabaseClient
import org.springframework.stereotype.Component
#Component
#Profile("test")
class TemplateFactory(
#Autowired val applicationContext: ApplicationContext
) {
private val beanFactory = applicationContext.autowireCapableBeanFactory as BeanDefinitionRegistry
fun registerTemplateBean(host: String, port: Int) {
val beanDefinition = GenericBeanDefinition()
beanDefinition.beanClass = R2dbcEntityTemplate::class.java
val args = ConstructorArgumentValues()
args.addIndexedArgumentValue(
0,
DatabaseClient.builder()
.connectionFactory(connectionFactory(host, port))
.bindMarkers(SqlServerDialect.INSTANCE.bindMarkersFactory)
.build()
)
args.addIndexedArgumentValue(1, DefaultReactiveDataAccessStrategy(SqlServerDialect.INSTANCE))
beanDefinition.constructorArgumentValues = args
beanFactory.registerBeanDefinition("R2dbcEntityTemplate", beanDefinition)
}
// fun entityTemplate(host: String = "localhost", port: Int = 1435) =
// R2dbcEntityTemplate(
// DatabaseClient.builder()
// .connectionFactory(connectionFactory(host, port))
// .bindMarkers(SqlServerDialect.INSTANCE.bindMarkersFactory)
// .build(),
// DefaultReactiveDataAccessStrategy(SqlServerDialect.INSTANCE)
// )
private fun connectionFactory(host: String, port: Int) =
MssqlConnectionFactory(
MssqlConnectionConfiguration.builder()
.host(host)
.port(port)
.username("sa")
.password("Password123##?")
.build()
)
}
And this is how my db initiliser looks like:
package com.wayfair.samworkgroupsservice.adapter.note
import com.wayfair.samworkgroupsservice.adapter.DBInitializerInterface
import com.wayfair.samworkgroupsservice.adapter.TemplateFactory
import org.springframework.beans.factory.annotation.Autowired
import org.springframework.data.r2dbc.core.R2dbcEntityTemplate
import org.testcontainers.containers.DockerComposeContainer
import org.testcontainers.containers.wait.strategy.Wait
import org.testcontainers.junit.jupiter.Container
import org.testcontainers.junit.jupiter.Testcontainers
import java.io.File
#Testcontainers
class NoteTagDBInitializer : DBInitializerInterface {
#Autowired
override lateinit var client: R2dbcEntityTemplate
#Autowired
lateinit var factory: TemplateFactory
override val sqlScripts = listOf(
"db/note/schema.sql",
"db/note/reset.sql",
"db/note/data.sql"
)
init {
factory.registerTemplateBean(
cont.getServiceHost("test-db-local_1", 1433),
cont.getServicePort("test-db-local_1", 1433)
)
}
companion object {
#Container
val cont: KDockerComposerContainer = KDockerComposerContainer("docker-compose.test.yml")
.withExposedService(
"test-db-local_1", 1433,
Wait.forListeningPort()
)
.withLocalCompose(true)
.also {
it.start()
val porttt = it.getServicePort("test-db-local_1", 1433)
print(porttt)
}
class KDockerComposerContainer(yamlFile: String) :
DockerComposeContainer<KDockerComposerContainer>(File(yamlFile))
}
}
I am not getting errors when trying to start this template factory with no useful error message,
But to be honest I don't know anymore if am putting effort into the correct solution, does anyone have any insight on how to pull this off or if I am doing anything wrong here?
So to summarise for production app it is fine, it starts based off of the url on application.yaml file and that's it, but for tests I need something dinamic with ports that will change everytime.
Thank you in advance ))
Spring already has a solution for your problem.
If you're using a quite recent Spring version (>= 5.2.5), you should utilize #DynamicPropertySource in order to adjust your test configuration properties with a dynamic value of the container database port. Read official spring documentation for more details and kotlin code examples.
If you're stuck with an older Spring version, the interface you need is ApplicationContextInitializer. See this spring github issue for a small example.

Netty client server login, how to have channelRead return a boolean

I'm writing client server applications on top of netty.
I'm starting with a simple client login server that validates info sent from the client with the database. This all works fine.
On the client-side, I want to use If statements once the response is received from the server if the login credentials validate or not. which also works fine. My problem is the ChannelRead method does not return anything. I can not change this. I need it to return a boolean which allows login attempt to succeed or fail.
Once the channelRead() returns, I lose the content of the data.
I tried adding the msg to a List but, for some reason, the message data is not stored in the List.
Any suggestions are welcome. I'm new... This is the only way I've figured out to do this. I have also tried using boolean statements inside channelRead() but these methods are void so once it closes the boolean variables are cleared.
Following is the last attempt I tried to insert the message data into the list I created...
import io.netty.channel.ChannelHandlerContext;
import io.netty.channel.ChannelInboundHandlerAdapter;
import java.util.Collection;
import java.util.Iterator;
import java.util.List;
import java.util.ListIterator;
public class LoginClientHandler extends ChannelInboundHandlerAdapter {
Player player = new Player();
String response;
public volatile boolean loginSuccess;
// Object message = new Object();
private Object msg;
public static final List<Object> incomingMessage = new List<Object>() {
#Override
public void channelRead(ChannelHandlerContext ctx, Object msg) throws Exception {
// incomingMessage.clear();
response = (String) msg;
System.out.println("channel read response = " + response);
incomingMessage.add(0, msg);
System.out.println("incoming message = " + incomingMessage.get(0));
}
How can I get the message data "out" of the channelRead() method or use this method to create a change in my business logic? I want it to either display a message to tell the client login failed and try again or to succeed and load the next scene. I have the business logic working fine but I can't get it to work with netty because none of the methods return anything I can use to affect my business logic.
ChannelInitializer
import io.netty.channel.ChannelInitializer;
import io.netty.channel.ChannelPipeline;
import io.netty.channel.socket.SocketChannel;
import io.netty.handler.codec.DelimiterBasedFrameDecoder;
import io.netty.handler.codec.Delimiters;
import io.netty.handler.codec.string.StringDecoder;
import io.netty.handler.codec.string.StringEncoder;
public class LoginClientInitializer extends ChannelInitializer <SocketChannel> {
#Override
protected void initChannel(SocketChannel ch) throws Exception {
ChannelPipeline pipeline = ch.pipeline();
pipeline.addLast("framer", new DelimiterBasedFrameDecoder(8192, Delimiters.lineDelimiter()));
pipeline.addLast("decoder", new StringDecoder());
pipeline.addLast("encoder", new StringEncoder());
pipeline.addLast("handler", new LoginClientHandler());
}
}
To get the server to write data to the client, call ctx.write here is a basic echo server and client example from the Netty in Action book. https://github.com/normanmaurer/netty-in-action/blob/2.0-SNAPSHOT/chapter2/Server/src/main/java/nia/chapter2/echoserver/EchoServerHandler.java
There are several other good examples in that repo.
I highly recommend reading the "netty in action" book if you're starting out with netty. It will give you a solid foundational understanding of the framework and how it's intended to be used.

How to test Apache HttpClient RequestConfig values are set correctly? No public getters present

I have this class to configure a HttpClient instance:
package com.company.fraud.preauth.service.feignaccertifyclient;
import com.company.fraud.preauth.config.ProviderClientConfig;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.apache.http.client.HttpClient;
import org.apache.http.client.config.RequestConfig;
import org.apache.http.conn.ssl.TrustSelfSignedStrategy;
import org.apache.http.impl.client.HttpClientBuilder;
import org.apache.http.ssl.SSLContextBuilder;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import java.security.KeyManagementException;
import java.security.KeyStoreException;
import java.security.NoSuchAlgorithmException;
#Slf4j
#Configuration
#RequiredArgsConstructor
public class FeignClientConfig {
private final ProviderClientConfig providerClientConfig;
public HttpClient buildHttpClient() throws NoSuchAlgorithmException, KeyStoreException, KeyManagementException {
RequestConfig.Builder requestBuilder = RequestConfig.custom();
requestBuilder.setConnectTimeout(providerClientConfig.getConnectionTimeout());
requestBuilder.setConnectionRequestTimeout(providerClientConfig.getConnectionRequestTimeout());
requestBuilder.setSocketTimeout(providerClientConfig.getSocketTimeout());
SSLContextBuilder builder = new SSLContextBuilder();
builder.loadTrustMaterial(null, new TrustSelfSignedStrategy());
return HttpClientBuilder.create()
.setMaxConnPerRoute(providerClientConfig.getMaxConnectionNumber())
.setDefaultRequestConfig(requestBuilder.build())
.setSSLContext(builder.loadTrustMaterial(null, new TrustSelfSignedStrategy()).build())
.build();
}
}
How to unit test this class, to see into the resulted HttpClient that these values are correctly set?
From the httpClient I cannot get access to its RequestConfig.
I am aware of these two posts:
How do I test a private function or a class that has private methods, fields or inner classes?
(the number of upvotes in this question shows that it is a concurrent and controversial topic in testing, and my situation may offer an example that why we should look into the inner state of an instance in testing, despite that it is private)
Unit test timeouts in Apache HttpClient
(it shows a way of adding an interceptor in code to check configure values, but I don't like it because I want to separate tests with functional codes)
Is there any way? I understand that this class should be tested, right? You cannot blindly trust it to work; and checking it "notNull" seems fragile to me.
This link may point me to the right direction:
https://dzone.com/articles/testing-objects-internal-state
It uses PowerMock.Whitebox to check internal state of an instance.
So I have checked into PowerMock.Whitebox source code, and it turns out reflection is used internally. And, as PowerMock is said to be not compatible with JUnit 5 yet(till now), and I don't want to add another dependency just for testing, so I will test with reflection.
package com.company.fraud.preauth.service.feignaccertifyclient;
import com.company.fraud.preauth.config.PreAuthConfiguration;
import com.company.fraud.preauth.config.ProviderClientConfig;
import com.company.fraud.preauth.config.StopConfiguration;
import org.apache.http.client.HttpClient;
import org.apache.http.client.config.RequestConfig;
import org.junit.jupiter.api.DisplayName;
import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.extension.ExtendWith;
import org.mockito.Mock;
import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.test.context.junit.jupiter.SpringExtension;
import java.lang.reflect.Field;
import static org.hamcrest.CoreMatchers.equalTo;
import static org.junit.Assert.assertThat;
import static org.mockito.Mockito.when;
#ExtendWith(SpringExtension.class)
#SpringBootTest(classes = {
PreAuthConfiguration.class,
StopConfiguration.class,
})
public class FeignClientConfigTest {
#Mock
private ProviderClientConfig providerClientConfig;
#Test
#DisplayName("should return HttpClient with defaultConfig field filled with values in providerClientConfig")
public void shouldReturnHttpClientWithConfiguredValues() throws Exception {
// given
when(providerClientConfig.getConnectionRequestTimeout()).thenReturn(30000);
when(providerClientConfig.getConnectionTimeout()).thenReturn(30);
when(providerClientConfig.getMaxConnNumPerRoute()).thenReturn(20);
when(providerClientConfig.getSocketTimeout()).thenReturn(10);
FeignClientConfig feignClientConfig = new FeignClientConfig(providerClientConfig);
// when
HttpClient httpClient = feignClientConfig.buildHttpClient();
// then
// I want to test internal state of built HttpClient and this should be checked
// I tried to use PowerMock.Whitebox, but then I found it uses reflection internally
// I don't want to introduce another dependency, and PowerMock is said not to be compatible with JUnit 5, so..
Field requestConfigField = httpClient.getClass().getDeclaredField("defaultConfig");
requestConfigField.setAccessible(true);
RequestConfig requestConfig = (RequestConfig)requestConfigField.get(httpClient);
assertThat(requestConfig.getConnectionRequestTimeout(), equalTo(30000));
assertThat(requestConfig.getConnectTimeout(), equalTo(30));
assertThat(requestConfig.getSocketTimeout(), equalTo(10));
}
}
Also, I answer the first question in OP about when to test private members in a class here
Whitebox was working for me. As it is not documented here I'm adding my version:
in my case wanted to test that the timeout is different from 0 to avoid deadlock
HttpClient httpClient = factory.getHttpClient();
RequestConfig sut = Whitebox.getInternalState(httpClient, "defaultConfig");
assertNotEquals(0, sut.getConnectionRequestTimeout());
assertNotEquals(0, sut.getConnectTimeout());
assertNotEquals(0, sut.getSocketTimeout());

Netflix Ribbon and Polling for Server List

I'm currently trying out the Netflix Ribbon library and I'm trying to dynamically update a list of available endpoints to load balance.
I've successfully created a httpResourceGroup that uses a configuration based server list, e.g.:
httpResourceGroup = Ribbon.createHttpResourceGroup("searchServiceClient",
ClientOptions.create()
.withMaxAutoRetriesNextServer(3)
.withLoadBalancerEnabled(true)
.withConfigurationBasedServerList(serverList))
However, I'd like to be able to use a DynamicServerList in the httpResourceGroup. I've managed to build a load balancer as follows:
LoadBalancerBuilder.<Server>newBuilder()
.withDynamicServerList(servicesList)
.buildDynamicServerListLoadBalancer();
but I can't find a way to swap out the load balancer configured by the httpResourceGroup ClientOptions.
Anyone know how I can do this?
The solution is to not specify withConfigurationBasedServerList() when constructing an HttpResourceGroup since this I believe this is meant for a fixed list though I am not sure. There are many ways to initialize a dynamic load balancer (typically you would never swap it out, but reuse the same load balancer and swap out new Servers as they become available or go away. The most straightforward way to do this might be via Archaius-based configuration.
Option 1
Create a config.properties file on the classpath containing the following
ribbon.NIWSServerListClassName=com.example.MyServerList
ribbon.NFLoadBalancerRuleClassName=com.netflix.loadbalancer.RoundRobinRule
Option 2
System.setProperty("ribbon.NIWSServerListClassName", "com.example.MyServerList");
System.setProperty("ribbon.NFLoadBalancerRuleClassName", "com.netflix.loadbalancer.RoundRobinRule");
Create a ServerList implementation
import java.util.Arrays;
import java.util.List;
import com.netflix.loadbalancer.Server;
import com.netflix.loadbalancer.ServerList;
public class MyServerList implements ServerList<Server> {
#Override
public final List<Server> getUpdatedListOfServers() {
// TODO do some fancy stuff here
return Arrays.asList(new Server("1.2.3.4", 8888), new Server("5.6.7.8", 9999));
}
#Override
public final List<Server> getInitialListOfServers() {
return Arrays.asList(new Server("1.2.3.4", 8888), new Server("5.6.7.8", 9999));
}
}
Run the code
HttpResourceGroup httpResourceGroup = Ribbon.createHttpResourceGroup("searchServiceClient",
ClientOptions.create()
.withMaxAutoRetriesNextServer(3);
HttpRequestTemplate<ByteBuf> recommendationsByUserIdTemplate = httpResourceGroup.newTemplateBuilder("recommendationsByUserId", ByteBuf.class)
.withMethod("GET")
.withUriTemplate("/users/{userId}/recommendations")
.withFallbackProvider(new RecommendationServiceFallbackHandler())
.withResponseValidator(new RecommendationServiceResponseValidator())
.build();
Observable<ByteBuf> result = recommendationsByUserIdTemplate.requestBuilder()
.withRequestProperty("userId", “user1")
.build()
.observe();
It sounds like you already have a ServerList implementation which is where you would do any event driven updates to your server list, but keep the load balancer the same.

Using JNDI to access a DataSource (Tomcat 6)

I have been trying to set up a Database Connection Pool for my test webapp just to learn how it's done really. I have managed to get a DataSource object connected to my database which supplies me with Connection objects now, so that's good.
I must admit I don't really know exactly how it's working. I wrote some test code to see if I could figure out how the InitialContext object is working:
package twittersearch.web;
import javax.servlet.*;
import javax.servlet.http.*;
import java.io.*;
import java.util.*;
import java.sql.*;
import javax.sql.*;
import javax.naming.*;
import twittersearch.model.*;
public class ContextTest extends HttpServlet {
public void doGet(HttpServletRequest request,
HttpServletResponse response)
throws IOException, ServletException {
Context ctx = null;
Context env = null;
try {
ctx = new InitialContext();
Hashtable<?, ?> h = ctx.getEnvironment();
Enumeration<?> keyEn = h.keys();
while(keyEn.hasMoreElements()) {
Object o = keyEn.nextElement();
System.out.println(o);
}
Enumeration<?> valEn = h.elements();
while(valEn.hasMoreElements()) {
Object o = valEn.nextElement();
System.out.println(o);
}
env = (Context)ctx.lookup("java:comp/env");
h = env.getEnvironment();
Enumeration<?> keys = h.keys();
Enumeration<?> values = h.elements();
System.out.println("Keys:");
while(keys.hasMoreElements()) {
System.out.println(keys.nextElement());
}
System.out.println("Values:");
while(values.hasMoreElements()) {
System.out.println(values.nextElement());
}
Collection<?> col = h.values();
for(Object o : col) {
System.out.println(o);
}
DataSource dataSource = (DataSource)env.lookup("jdbc/twittersearchdb");
Connection conn = dataSource.getConnection();
if(conn instanceof Connection) {
System.out.println("Have a connection from the pool");
}
} catch(Exception e) {
e.printStackTrace();
}
}
}
This gives me output of:
java.naming.factory.initial
java.naming.factory.url.pkgs
org.apache.naming.java.javaURLContextFactory
org.apache.naming
Have a connection from the pool
Keys:
Values:
Have a connection from the pool
What I don't understand
I have got the InitialContext object which, as I understand it, I should be able to get a Hashtable from with keys and values of all the bindings for that context. As the first four lines of the output show, there were only two bindings.Yet I am able to use ctx.lookup("java:comp/env") to get another context that has bindings for Resources for my webapp. There was no "java:comp/env" in the keys from the test output from the InitialContext object. Where did that come from?
Also as you can see I tried to printout the keys and values from the java:comp/env context and got no output and yet I am able to use env.lookup("jdbc/twittersearchdb") which gets me the DataSource that I have specified in my context.xml. Why do I have no output for the bindings for the "java:comp/env" context?
Can I just confirm that as I have specified a Resource element in my context.xml, the container is creating a DataSource onject on deployment of the webapp and the whole Context / InitialContext thing is just a way of using JNDI to access the DataSource object? And if that's the case, why is JNDI used when it seems easier to me to create a DataSource in an implementation of ServletContextListener and have the datasource as a ServletContext attribute?
Does the DataSource object actually manage the ConnectionPool or is that the Container and so is the DataSource object just a way of describing the connection?
How do we access the container directly? What is the object that acctually represents the container? Is it ServletContext? I'm just trying to find out what the container can do for me.
Apologies for the length of this post. I really want to clear up these issues because I'm sure all this stuff is used in every webapp so I need to have it sorted.
Many thanks in advance
Joe