I have jMeter test case already written, it has requests for logging in and some POST requests to my table.
Now I want to measure render time of the page (response of the POSTS).
I want to use Selenium so I read:
Running Selenium scripts with JMeter
The problem is that i want Selenium to use the same session (in other words: to be already logged in) as logging in was handled by jMeter already.
How can i archive that ?
My TestPlan:
[UPDATE]
#Dmitri T
Thanks for answer! I did what you have suggested but it still don't work. Maybe I am doing something wrong but still just after Selenium opens browser it goes to login page. I put JSR223 PostProcessor under my LOGIN POST request and this is my WebDriver Sampler:
var pkg = JavaImporter(org.openqa.selenium);
var support_ui = JavaImporter(org.openqa.selenium.support.ui.WebDriverWait);
var wait = new support_ui.WebDriverWait(WDS.browser, 5000);
WDS.sampleResult.sampleStart();
WDS.sampleResult.getLatency();
WDS.log.info("Sample started");
WDS.browser.get(WDS.parameters);
WDS.log.info("LOGGING INTO: " + (WDS.parameters))
var cookieManager = WDS.vars.getObject('cookieManager')
for (var i=0; i < cookieManager.getCookieCount(); i++) {
var jmeterCookie = cookieManager.getCookies().get(i)
var seleniumCookie = new org.openqa.selenium.Cookie(jmeterCookie.name, jmeterCookie.value, jmeterCookie.domain, jmeterCookie.path, java.util.Date.from(java. time.Instant.ofEpochMilli(jmeterCookie.expiresMillis)), jmeterCookie.secure)
WDS.browser.manage().addCookie(seleniumCookie)
}
java.lang.Thread.sleep(5000)
WDS.sampleResult.sampleEnd();
{UPDATE 2}
Ok, i think that htere is something wrong with Cookies in all Thread Group.
I have [no cookies] on every request:
In order to pass the "session" you need to copy all the cookies from JMeter's HTTP Cookie Manager into the Selenium session.
This can be done in the WebDriver Sampler directly as follows:
Add JSR223 PostProcessor as a child of the request which session you want to copy
Put the following code into "Script" area
vars.putObject('cookieManager', sampler.getCookieManager())
it will store the current state of the HTTP Cookie Manager into JMeter Variables
In the WebDriver Sampler you can copy the cookies from the HTTP Cookie Manager and add them to the WebDriver instance using WDS.browser.manage().addCookie() function as follows:
var cookieManager = WDS.vars.getObject('cookieManager')
for (var i=0; i < cookieManager.getCookieCount(); i++) {
var jmeterCookie = cookieManager.getCookies().get(i)
var seleniumCookie = new org.openqa.selenium.Cookie(jmeterCookie.name, jmeterCookie.value, jmeterCookie.domain, jmeterCookie.path, java.util.Date.from(java.time.Instant.ofEpochMilli(jmeterCookie.expiresMillis)), jmeterCookie.secure)
WDS.browser.manage().addCookie(seleniumCookie)
}
Related
I've been trying to automate a simple process for two days now. I started with a web scraper (as I'd just finished a project using that), but quickly found this wasn't a good option. The site I need access to runs some scripts when the form posts, and I can't get it to work with the scraper. So I turned to HttpWebRequest and HttpWebResponse. Whole lot of banging my head against the wall, no dice. So I tried the Selenium ChromeDriver, and so far that's the closest I've come to getting this to work. I need to:
Load login page and submit login form (can't send in URL - doesn't work).
Load report form page.
Change field values.
Submit report form.
Download CSV response.
Here's my current code:
var username = _configuration.GetValue<string>("LoginCreds:username");
var password = _configuration.GetValue<string>("LoginCreds:password");
var driver = new ChromeDriver(#"C:\Users\path\to\libs");
driver.Url = "https://mydomain.loginpage.com";
driver.Navigate();
var usernameField = driver.FindElementById("username");
usernameField.SendKeys(username);
var passwordField = driver.FindElementById("password");
passwordField.SendKeys(password);
driver.FindElementById("submit").Submit();
// Handle browser version alert.
var alert = driver.SwitchTo().Alert();
alert.Dismiss();
var html = driver.PageSource;
Console.WriteLine(html); // This is the HTML of the authenticated page - as expected.
Console.ReadKey();
// Load report form page.
driver.Navigate().GoToUrl("https://mydomain.reports.com");
html = driver.PageSource;
Console.WriteLine(html); // Now I get the login page HTML - authentication is lost.
Console.ReadKey();
So authentication seems to be working, but it doesn't persist. I need to be able to move about on the site after authenticating.
It's working now with the code I posted. Turned out I had multiple windows open, authenticated to that site, so even refreshing the page was posting back to the login page. Turns out that the ChromeDriver did the trick just fine. I was even able to download the CSV file I need, which is dynamically generated, so there isn't a static URL I can map to for downloading. Here's the working code:
var username = _configuration.GetValue<string>("LoginCreds:username");
var password = _configuration.GetValue<string>("LoginCreds:password");
var chromeOptions = new ChromeOptions();
chromeOptions.AddUserProfilePreference("download.default_directory", #"C:\Users\path\to\download\WorkingDirectory");
chromeOptions.AddUserProfilePreference("diable-popup-blocking", "true");
var driver = new ChromeDriver(#"C:\Users\path\to\libs", chromeOptions);
driver.Url = "https://mydomain.loginpage.com";
driver.Navigate();
driver.FindElementById("username").SendKeys(username);
driver.FindElementById("password").SendKeys(password);
driver.FindElementById("submit").Submit();
// Handle browser version alert.
driver.SwitchTo().Alert().Dismiss();
// Load report form page.
driver.Navigate().GoToUrl("https://mydomain.reports.com");
driver.FindElementByName("Option1").Click();
driver.FindElementByName("Option2").Click();
driver.FindElementByName("Option3").Click();
driver.FindElementById("submit").Submit();
Thread.Sleep(30000);
driver.Close();
driver.Quit();
I wasn't able to find an elegant solution to close the browser and the console when the download completes. There are solutions posted that scan the download director for a completed file, but that was a lot of effort for little yield. Waiting for 30 seconds gives the CSV file plenty of time to download, and the browser/console cleanup isn't time-sensitive.
I try to use content query in web application but it throw an exception " Lucene.Net.Store.AlreadyClosedException: this IndexReader is closed". Please give help me resolve that problem.
var startSettings = new RepositoryStartSettings
{
Console = Console.Out,
StartLuceneManager = true, // <-- this is necessary
IsWebContext = false,
PluginsPath = AppDomain.CurrentDomain.BaseDirectory,
};
using (Repository.Start(startSettings))
{
var resultQuery = ContentQuery.Query("+InTree:#0 + DisplayName:*#1*", null, folderPath, q);
}
The recommended way to connect to Sense/Net from a different application (app domain) is through the REST API. It is much easier to maintain and involves less configuration (the only exception is where you are working inside the Sense/Net application itself, or you only have a single application and you do not want to access Sense/Net from anywhere else, and you are willing to deal with a local index of Sense/Net and all the config values it needs, etc).
Connecting through the REST API does not mean you have to send HTTP requests manually (although that is also not complicated at all): there is a .Net client library which does that for you. You can access all content metadata or binaries through the client, you can upload files, query content, manage permissions, etc.
// loading a content
dynamic content = await Content.LoadAsync(id);
DateTime date = content.BirthDate;
// querying
var results = await Content.QueryAsync(queryText);
Install: https://www.nuget.org/packages/SenseNet.Client
Source and examples: https://github.com/SenseNet/sn-client-dotnet
To use it in a web application, you have to do the following:
initialize the client context once, at the beginning of the application life cycle (e.g. app start)
if you need to make requests to Sense/Net in the name of the currently logged in user (e.g. because you want to query for documents accessible by her), than you have to create a new ServerContext object for every user with the username/password of that user, and provide this object to any client call (e.g. load or save content methods).
var sc = new ServerContext
{
Url = "http://example.com",
Username = "user1",
Password = "asdf"
};
var content = await Content.LoadAsync(id, sc);
The project i'm working on has a api behind a login.
I'm using behat with mink to login:
Scenario: Login
Given I am on "/login/"
And I should see "Login"
When I fill in "_username" with "test"
And I fill in "_password" with "test"
And I press "_submit"
Then I should be on "/"
This works..
However, the login session is not stored whenever i want to do the following using the WebApiContext:
Scenario: Getting the list of pages
When I send a GET request to "/api/pages.json"
Then print response
I'm using both scenarios in the same feature. My FeatureContext class looks something like this:
class FeatureContext extends MinkContext
{
public function __construct(array $parameters)
{
$context = new WebApiContext($parameters['base_url']);
$context->getBrowser()->getClient()->setCookieJar(new \Buzz\Util\CookieJar());
$this->useContext('web', $context);
}
}
I added the cookiejar idea from this issue without success.. When i print the response i just see the HTML page from the login screen..
Does anyone have any idea if i'm going at this totally the wrong way or am i somewhat in the right direction?
I am successfully using the same method. I don't think there's a standard way of doing this. As far as you understand the cookie basics you should be able to implement the solution.
In a common scenario, a client sends an authentication request to the server with some credentials, the servers validates it, starts an authenticated session and sends back a cookie with that session id. All following requests contain that id, so the server can recognise the callee. A specific header can be used instead of the cookie, or a database can be used instead of the session, but the principle is the same and you can (relatively) easily simulate it with Mink.
/**
* Start a test session, set the authenticated user and set the client's cookie.
*
* #Given /^I am signed in$/
*/
signIn()
{
session_start();
$_SESSION['user'] = 'jos';
$this->getSession()->getDriver()->setCookie(session_name(), session_id());
session_commit();
}
The above step definition (Behat 3) is the basics of it, you manually create the authenticated session and set to the client it's id. That must be also what the other example illustrates.
PHP's sessions can be problematic when you start doing more complex things and there are a couple of big underwater rocks with this solution. If you want to run assertions from both perspectives (the client and the server) you might often need to have your sessions synced. This can be done by updating the cookie before all Mink steps and reloading the session after.
/**
* #beforeStep
* #param BeforeStepScope $scope
*/
public function synchroniseClientSession(BeforeStepScope $scope)
{
// Setup session id and Xdebug cookies to synchronise / enable both.
$driver = $this->getSession()->getDriver();
// Cookie must be set for a particular domain.
if ($driver instanceof Selenium2Driver && $driver->getCurrentUrl() === 'data:,') {
$driver->visit($this->getMinkParameter('base_url'));
}
// Also enables the debugging support.
$driver->setCookie(session_name(), session_id());
$driver->setCookie('XDEBUG_SESSION', 'PHPSTORM');
}
/**
* #afterStep
* #param AfterStepScope $scope
*/
public function synchroniseServerSession(AfterStepScope $scope)
{
$driver = $this->getSession()->getDriver();
// Only browser kit driver, only initiated requests, only not repeating requests.
if (!$driver instanceof BrowserKitDriver) {
return;
} elseif (($request = $driver->getClient()->getRequest()) === null) {
return;
} elseif ($request === self::$request) {
return;
}
// Your logic for reloading the session.
self::$request = $request;
}
The biggest problem I had was the session reloading. This might be due to my framework of choice, which I doubt. The very first code snippet has session_commit(), which saves and closes the session. In theory in the following step definitions you must be able to session_id(/* session id from the cookie… */); and session_start();, but in practice that didn't work and no session data was actually loaded from the file, though the session did start. To solve this I created a custom session manager with reload() method using session save handler.
Second problem is where you cannot simply close the session without either writing it or destroying it (the support is added in PHP 5.6) on which relies the reloading itself. I reinvented the wheel with a flag for the session manager which tells it whether to write or just to close it.
:)
I`m using JMeter for load testing where I have to call the upload Image API through an HTTP request, and to achieve this I have to convert an image into a compressed byte array to send it out as post data through an HTTP request.
Can anyone show me how it would be possible through JMeter?
Your help would really be appreciated.
There are several options on how you can proceed:
You can use HTTP Raw Request Sampler (available through JMeter Plugins site) which gives you full control on what, how and where you send.
Have you tried enabling Use multipart/form-data for POST for HTTP Request Sampler? This is how files should be uploaded as per RFC-1867.
If your use case is specific and none of the above is applicable, you can always use JMeter Scripting extensions. For example if you add a Beanshell Pre Processor to your HTTP Request which performs file upload with something like:
FileInputStream in = new FileInputStream("/home/glinius/401.png");
ByteArrayOutputStream bos = new ByteArrayOutputStream();
byte[] buffer = new byte[1024];
for (int i; (i = in.read(buffer)) != -1; ) {
bos.write(buffer, 0, i);
}
in.close();
byte[] imageData = bos.toByteArray();
bos.close();
vars.put("imageData", new String(imageData));
You'll be able to add ${imageData} parameter in your POST request.
Yes, I follow this method "add a Beanshell Pre Processor to your HTTP Request", and successful.
For my case, I also add a "HTTP Header Manager", specify: "Content-Encoding:gzip", "Content-Type:"application/x-www-form-urlencoded", "Accept:/".
And, set String encoding by: vars.put("binaryData", new String(binThrift, "ISO-8859-1"));
HTTP Header Manager
Beanshell Pre Processor
HTTP Request
Real Request
I want to use the Jenkins Remote API, and I am looking for safe solution. I came across Prevent Cross Site Request Forgery exploits and I want to use it, but I read somewhere that you have to make a crumb request.
How do I get a crumb request in order to get the API working?
I found this https://github.com/entagen/jenkins-build-per-branch/pull/20, but still I don't know how to fix it.
My Jenkins version is 1.50.x.
Authenticated remote API request responds with 403 when using POST request
I haven't found this in the documentation either. This code is tested against an older Jenkins (1.466), but should still work.
To issue the crumb use the crumbIssuer
// left out: you need to authenticate with user & password -> sample below
HttpGet httpGet = new HttpGet(jenkinsUrl + "crumbIssuer/api/json");
String crumbResponse = toString(httpclient, httpGet);
CrumbJson crumbJson = new Gson().fromJson(crumbResponse, CrumbJson.class);
This will get you a response like this
{"crumb":"fb171d526b9cc9e25afe80b356e12cb7","crumbRequestField":".crumb"}
This contains two pieces of information you need
the field name with which you need to pass the crumb
the crumb itself
If you now want to fetch something from Jenkins, add the crumb as header. In the sample below I fetch the latest build results.
HttpPost httpost = new HttpPost(jenkinsUrl + "rssLatest");
httpost.addHeader(crumbJson.crumbRequestField, crumbJson.crumb);
Here is the sample code as a whole. I am using gson 2.2.4 to parse the response and Apache's httpclient 4.2.3 for the rest.
import org.apache.http.auth.*;
import org.apache.http.client.*;
import org.apache.http.client.methods.*;
import org.apache.http.impl.client.*;
import com.google.gson.Gson;
public class JenkinsMonitor {
public static void main(String[] args) throws Exception {
String protocol = "http";
String host = "your-jenkins-host.com";
int port = 8080;
String usernName = "username";
String password = "passwort";
DefaultHttpClient httpclient = new DefaultHttpClient();
httpclient.getCredentialsProvider().setCredentials(
new AuthScope(host, port),
new UsernamePasswordCredentials(usernName, password));
String jenkinsUrl = protocol + "://" + host + ":" + port + "/jenkins/";
try {
// get the crumb from Jenkins
// do this only once per HTTP session
// keep the crumb for every coming request
System.out.println("... issue crumb");
HttpGet httpGet = new HttpGet(jenkinsUrl + "crumbIssuer/api/json");
String crumbResponse= toString(httpclient, httpGet);
CrumbJson crumbJson = new Gson()
.fromJson(crumbResponse, CrumbJson.class);
// add the issued crumb to each request header
// the header field name is also contained in the json response
System.out.println("... issue rss of latest builds");
HttpPost httpost = new HttpPost(jenkinsUrl + "rssLatest");
httpost.addHeader(crumbJson.crumbRequestField, crumbJson.crumb);
toString(httpclient, httpost);
} finally {
httpclient.getConnectionManager().shutdown();
}
}
// helper construct to deserialize crumb json into
public static class CrumbJson {
public String crumb;
public String crumbRequestField;
}
private static String toString(DefaultHttpClient client,
HttpRequestBase request) throws Exception {
ResponseHandler<String> responseHandler = new BasicResponseHandler();
String responseBody = client.execute(request, responseHandler);
System.out.println(responseBody + "\n");
return responseBody;
}
}
Or you can use Python and requests instead
req = requests.get('http://JENKINS_URL/crumbIssuer/api/xml?xpath=concat(//crumbRequestField,":",//crumb)', auth=(username, password))
print(req.text)
will give you the name and the crumb:
Jenkins-Crumb:e2e41f670dc128f378b2a010b4fcb493
This Python function gets the crumb, and additionally uses the crumb to post to a Jenkins endpoint. This is tested with Jenkins 2.46.3 with CSRF protection turned on:
import urllib.parse
import requests
def build_jenkins_job(url, username, password):
"""Post to the specified Jenkins URL.
`username` is a valid user, and `password` is the user's password or
(preferably) hex API token.
"""
# Build the Jenkins crumb issuer URL
parsed_url = urllib.parse.urlparse(url)
crumb_issuer_url = urllib.parse.urlunparse((parsed_url.scheme,
parsed_url.netloc,
'crumbIssuer/api/json',
'', '', ''))
# Use the same session for all requests
session = requests.session()
# GET the Jenkins crumb
auth = requests.auth.HTTPBasicAuth(username, password)
r = session.get(crumb_issuer_url, auth=auth)
json = r.json()
crumb = {json['crumbRequestField']: json['crumb']}
# POST to the specified URL
headers = {'Content-Type': 'application/x-www-form-urlencoded'}
headers.update(crumb)
r = session.post(url, headers=headers, auth=auth)
username = 'jenkins'
password = '3905697dd052ad99661d9e9f01d4c045'
url = 'http://jenkins.example.com/job/sample/build'
build_jenkins_job(url, username, password)
Meanwhile you can generate an API token in order to prevent having to include your password in the source code provided by the solutions above:
https://wiki.jenkins.io/display/JENKINS/Authenticating+scripted+clients
Refer - https://support.cloudbees.com/hc/en-us/articles/219257077-CSRF-Protection-Explained
If you authenticate with a username and a user API token then a crumb is not needed from Jenkins 2.96 weekly/2.107 LTS. For more information please refer to CSRF crumb no longer required when authenticating using API token or JENKINS-22474.
User cheffe's answer helped 90%. Thanks for giving us the right direction.
The missing 10% revolved around HTTP username and password authentication.
Since the Codenameone Java API I was using did not have the Authentication Class,
new UsernamePasswordCredentials(usernName, password));
I used:
String apiKey = "yourJenkinsUsername:yourJenkinsPassword";
httpConnection.addRequestHeader("Authorization", "Basic " + Base64.encode(apiKey.getBytes()));
User cheffe's Java snippet worked great for me on Jenkins v2.89.3 (Eclipse.org) and another Jenkins instance I use, at v2.60.3 (once enabled1).
I've added this to a Maven mojo2 I use for pushing locally-edited config.xml changes back to the server.
1 CSRF Protection
2 Hudson job sync plugin
In any of these answers I didn't find an option to use Jenkins API token.
I really tried all of these options but if you're enabling CSRF protection, you should access Jenkins APIs with Jenkins API token instead of normal password.
This token can be generated by each individual user in the user config page.
The token can be used as follows-
JenkinsApi::Client.new(server_url: jenkins_url, username: jenkins_user, password: jenkins_token)
P.S. - This initialization is for a Ruby Jenkins API client