Test file download with Capybara and Cucumber - webkit

I am trying to test a download using Capybara and Cucumber.
The test steps look like:
When(/^I export to CSV$/) do
export_button = find("div.results-table input[type=\"submit\"]")
export_button.click
end
Then(/^I should be prompted to download the CSV$/) do
Capybara.current_driver = :webkit #switch driver so we can see the response_headers
page.response_headers['Content-Disposition'].should include("filename=\"transactions\"")
end
I had to add the capybara webkit driver in the middle of the test so that I can use the response_headers, but the response_headers seem to return an empty object. I am using the default driver before that because I need to click the button, but I wonder if switching drivers like this is why I am not getting anything in the response_header?

I have done it in my recent project as shown below
Given Download folder for export is empty
And Joe clicks "Download Csv" button
Then The contents of the downloaded csv should be:
|TEAM_ID | TEAM_EXTERNAL_ID | TYPE | REG_OPTION | TEAM_REG_BRACKET | RACE_NAME | WAVE | BIB | BRACKET | STATUS | TEAM_NAME | TEAM_TYPE | ATHLETE_COUNT | MIN_MEMBERS | MAX_MEMBERS | TEAM_MEMBERS |
| 8 | | TEAM | 10k Aggregate | N/A | 10k | Universal | 208 | Overall | DNF | Team 08 | Aggregate |0 | | | |
And the capybara for this cucumber will be like
Given(/^Download folder for export is empty$/) do
FileUtils.rm_rf('/home/vagrant/Downloads/')
end
And(/^(\S*) clicks "([^"]*)" button$/) do |user, arg|
button = find_button(arg)
button.click
end
And /^The contents of the downloaded csv should be:$/ do |table|
for i in 0..5
if(Dir.glob('/home/vagrant/Downloads/*.csv'))
break;
end
sleep(1); // if not found wait one second before continue looping
end
if(Dir.glob('/home/vagrant/Downloads/*.csv'))
Dir['/home/vagrant/Downloads/*'].each do |file_name|
arr = CSV.read(file_name, :col_sep => "\t", :row_sep => "\r\n", encoding: "UTF-16:UTF-8")
table.raw[0...table.raw.length].each_with_index do |row, row_index|
row.each_with_index do |value, index|
if arr[row_index][index] == nil
arr[row_index][index] = ""
end
if index == 1
else
puts "#{index}" + 'Value in table = ' + "#{value} + 'Value in file' + #{arr[row_index][index]}"
value.should eq arr[row_index][index]
end
end
end
end
else
puts "/home/vagrant/Downloads/*.csv file not found!"
end
end
Hope this resolves you problem for downloading CSV and verifying its contents too :)

Related

Where can i find the annotations for the api.order.search controller?

I need to write a new api endoint, very similar to the api/search/order endpoint using a custom implementation. Basically i want to add a new response format for another system.
This will also be a new route like api/custom/search/order.
My approach is that i want to write a new controller by extending the existing controller, defined as follows:
+--------------+---------------------------------------------------------------------------------------+
| Property | Value |
+--------------+---------------------------------------------------------------------------------------+
| Route Name | api.order.search |
| Path | /api/search/order{path} |
| Path Regex | {^/api/search/order(?P<path>(?:\/[0-9a-f]{32}\/(?:extensions\/)?[a-zA-Z-]+)*\/?)$}sDu |
| Host | ANY |
| Host Regex | |
| Scheme | ANY |
| Method | POST |
| Requirements | path: (\/[0-9a-f]{32}\/(extensions\/)?[a-zA-Z-]+)*\/? |
| | version: \d+ |
| Class | Symfony\Component\Routing\Route |
| Defaults | _controller: Shopware\Core\Framework\Api\Controller\ApiController::search() |
| | _routeScope: array (0 => 'api',) |
| | entityName: order |
| Options | compiler_class: Symfony\Component\Routing\RouteCompiler |
| | utf8: true |
+--------------+---------------------------------------------------------------------------------------+
From my current setup i was able to write a new controller but the controller can not be found in the routes as there is no annotation in the original one. I could add a custom annotation but i was looking at the original definition and actually there is no annotation.
public function search(Request $request, Context $context, ResponseFactoryInterface $responseFactory, string $entityName, string $path): Response
{
[$criteria, $repository] = $this->resolveSearch($request, $context, $entityName, $path);
$result = $context->scope(Context::CRUD_API_SCOPE, function (Context $context) use ($repository, $criteria): EntitySearchResult {
return $repository->search($criteria, $context);
});
$definition = $this->getDefinitionOfPath($entityName, $path, $context);
return $responseFactory->createListingResponse($criteria, $result, $definition, $request, $context);
}
My question is: Is there something like a dynamic annotation which will automatically be created? I can not find any information about how the path for /api/search/order{path} is defined.
/**
* #Route(defaults={"_routeScope"={"api"}})
*/
class OrderActionController extends ApiController
{
public function search(Request $request, Context $context, ResponseFactoryInterface $responseFactory, string $entityName, string $path): Response
{
}
}
I did figure it out. The annotations for the api.search.order are defined in the class Shopware\Core\System\CustomEntity\Api\CustomEntityApiController.
Shopware uses a dynamic annotation which will be used to create the routes. I was looking for a specific annotation and therefore was not able to find it.
#Route(
"/api/search/custom-entity-{entityName}{path}",
name="api.custom_entity_entity.search",
requirements={"path"="(\/[0-9a-f]{32}\/(extensions\/)?[a-zA-Z-]+)*\/?$"},
methods={"POST"}
)

istanbul generates empty report

I wanted to launch istanbul in my project, however no matter what I do reports shows only zeros. like below :
----------|---------|----------|---------|---------|-------------------
File | % Stmts | % Branch | % Funcs | % Lines | Uncovered Line #s
----------|---------|----------|---------|---------|-------------------
All files | 0 | 0 | 0 | 0 |
----------|---------|----------|---------|---------|-------------------
Even though it shows above the coverage report list of the test that passed. For simplicity I created super simple project for replication where I have only one function in indentity.js file :
export const identity = (x) => x;
and one simple test licated in test.js file
import { identity } from "./identity.js";
import assert from "assert";
describe("identity", function () {
it("should return passed argument", () => {
assert.equal(identity(1), 1);
assert.equal(identity("string"), "string");
assert.deepEqual(identity({ a: 1 }), { a: 1 });
});
});
and after I run nyc mocha I expect to see 100% coverage but what I have is the same table with zeros.
Is there some settings I missed to see proper coverage report ?
Thx in advance.

how to call a feature file to generated a Get request , for each element in an ArrayList or JsonArray ( Created by Java function )

basically I want to achieve below result , where the list used in Examples: is dynamic
Scenario Outline:
def ss = 'https:testingurl/'+ < spaceCode > + 'trailPath';
Given url ss
And header Authorization = autGetToken()
When method get
Then status 200
Examples:
| spaceCode |
| space1|
| spac2|
| spaceAbc05|
| space.o2|
| spacesacc|
| spacere |
So I created one.feture and tried to call it from other feature file
one.feature
Background:
def ss = 'https:testingurl/'+ < spaceCode > + 'trailPath';
Scenario: need to run this scenario for each member of the spaceList ArrayList / JsonArray
Given url ss
And header Authorization = autGetToken()
When method get
Then status 200
* print response
* def count = response.value.length
* print count
* karate.set('total', karate.get('total') + karate.get('count') )
* print total
question
how to run above file for each element of the "spaceList" which is an Java ArrayList, i am creating this ArrayList by calling method getSpaceList()
i have tried to call it from other feature file like below
Method1
caller.feature
Background:
* def total = 0
* def helper = Java.type('shared.Helper')
Scenario Outline: calling other feature file for each element of the spaceList Array
def result = call read('one.feature')
Examples:
| spaceCode |
| helper.getSpaceList() |
Method2
caller.feature
Background:
* def total = 0
* def helper = Java.type('shared.Helper')
* def spaceList = helper.getSpaceList() // this returns around 20 different elements from a sql
Scenario Outline: calling other feature file for each element of the spaceList Array
def result = call read('one.feature')
Examples:
| spaceCode |
| spaceList |
i have also tried karate.forEach and karate.setUp
Try this example. Note how it will make two POST requests. Once you understand this, you should be able to do what you want.
Feature:
Scenario Outline:
* url 'https://httpbin.org/anything'
* request __row
* method post
* status 200
Examples:
| [{ a: 1 }, { a: 2 }] |

Using karate-config parameters in a feature file

The karate header examples do not show how to access config values other than baseUrl. When I switch environments (passing in -Dkarate.env=qual as part of the run command) then baseUrl is set correctly.
The problem is, I want to use other config values as shown here but when I run the test, it fails to access config.ApiKey correctly. Instead I get this error
html report:
file:/C:/bitbucket/karate-checkdigit-api/target/surefire-reports/TEST-features.checkdigitapi.VA.html
Tests run: 250, Failures: 0, Errors: 50, Skipped: 175, Time elapsed: 4.112 sec <<< FAILURE!
* def secretKey = config.apiKey(| XYZ | 2110974841 | 204 | Valid |) Time elapsed: 0.005 sec <<< ERROR!
java.lang.RuntimeException: no variable found with name: config
at com.intuit.karate.Script.getValuebyName(Script.java:323)
at com.intuit.karate.Script.evalJsonPathOnVarByName(Script.java:378)
at com.intuit.karate.Script.eval(Script.java:309)
at com.intuit.karate.Script.eval(Script.java:194)
at com.intuit.karate.Script.assign(Script.java:656)
at com.intuit.karate.Script.assign(Script.java:587)
at com.intuit.karate.StepDefs.def(StepDefs.java:265)
at ✽.* def secretKey = config.apiKey(features/checkdigitapi/XYZ.feature:6)
My .feature file and karate-config.js are below.
XYZ.feature
#regression
Feature: Checkdigit Algorithm API
Background:
* url baseUrl
* def secretKey = config.apiKey
* configure ssl = true
Scenario Outline: Testing XYZ algorithm
* configure headers = { KeyId: secretKey, Accept: 'application/json' }
Given path 'headers'
And param url = baseUrl
And params { customerId: '<custcode>', algoId: '<algo>' }
When method get
Then status <val>
Examples:
| algo | custcode | val | comment |
| XYZ | 2110974841 | 204 | Valid |
| XYZ | 7790011614 | 204 | Valid |
| XYZ | 5580015174 | 204 | Valid |
| XYZ | 2110974840 | 400 | expected check digit 1 |
| XYZ | 211097484 | 400 | not 10 digits |
| XYZ | 211097484x | 400 | not numeric |
karate-config.js
function() {
//set up runtime variables based on environment
//get system property 'karate.env'
var env = karate.env;
if (!env) { env = 'dev'; } // default when karate.env not set
// base config
var config = {
env: env,
baseUrl: 'https://localapi.abc123.example.com/api/v1/validate/customerid',
apiKey: ''
}
//switch environment
if (env == 'dev') {
config.baseUrl = 'https://devapi.abc123.example.com/api/v1/validate/customerid';
config.apiKey = 'fake-1ba403ca8938176f3a62de6d30cfb8e';
}
else if (env == 'qual') { //Pre-production environment settings
config.baseUrl = 'https://qualapi.abc123.example.com/api/v1/validate/customerid';
config.apiKey = 'fake-d5de2eb8c0920537f5488f6535c139f2';
}
karate.log('karate.env =', karate.env);
karate.log('config.baseUrl =', config.baseUrl);
karate.log('config.apiKey =', config.apiKey);
return config;
}
(similar issue here, using a separate headers.js: https://github.com/intuit/karate/issues/94)
Keep in mind that all the keys within the JSON object returned by karate-config.js will be injected as variables, and nothing else. So you will not be able to refer to config, but you will certainly be able to refer to apiKey.
I think if you make this simple change, things will start working:
* def secretKey = apiKey
Also, I think you have a problem in the first line of the scenario, it should be:
* configure headers = { KeyId: '#(secretKey)', Accept: 'application/json' }
FYI my final, correctly working XYZ.feature file looks like this now.
The line Given path 'headers' caused header info to creep into the url so it's removed.
XYZ.feature
#regression
Feature: Checkdigit Algorithm API
Background:
* url baseUrl
* def secretKey = apiKey
* configure ssl = true
Scenario Outline: Testing XYZ algorithm
* configure headers = { KeyId: '#(secretKey)', Accept: 'application/json' }
Given url baseUrl
And params { customerId: '<custcode>', algoId: '<algo>' }
When method get
Then status <val>
Examples:
[...]

GORM (varchar .save(), non DATE): ORA-01861: literal does not match format string

Hello I'm receiving a very strange error: ORA-01861: literal does not match format string. All my internet searching has related to DATE issues. Mine is just to save a simple string. If you see the issue please let me know how to fix it.
Column of issue
TREND: VARCHAR2(31, CHAR), no constraints
Mapped in GORM by:
String trend
...
static constraints = {
trend(nullable: true, blank: true)
... } ...
static mapping = {
trend column: "TREND", sqlType: "varchar(31)"
...}
Issue method:
def fix_trend(){
// There should ever only be three values found in the trend column
// of the Reports table: "TRENDING UP", "TRENDING STEADY", "TRENDING DOWN"
println "Running groovy database procedure : fix_trend"
StatusReport.list().each{
String trend = it.trend
if (trend != 'TRENDING UP' && trend != 'TRENDING STEADY' && trend != 'TRENDING DOWN'){
trend = trend?.toUpperCase()
if (trend == null){
trend = 'TRENDING STEADY'
}
else if (trend.contains('UP')){
trend = 'TRENDING UP';
}
else if (trend.contains('DOWN')){
trend = 'TRENDING DOWN';
}
else{
trend = 'TRENDING STEADY';
}
it.trend = trend;
it.save();
}
}
println "fix_trend completed"
}
The purpose of the method is to ensure all the values are either TRENDING UP, TRENDING DOWN, or TRENDING STEADY.
Stacktrace:
Running groovy database procedure : fix_trend
fix_trend completed
Error |
2015-01-05 10:53:02,392 [http-bio-8080-exec-4] ERROR spi.SqlExceptionHelper - ORA-01861: literal does not match format string
Error |
2015-01-05 10:53:02,481 [http-bio-8080-exec-4] ERROR errors.GrailsExceptionResolver - SQLDataException occurred when processing request: [GET] /investigator/statusReports/selection
ORA-01861: literal does not match format string
. Stacktrace follows:
Message: ORA-01861: literal does not match format string
Line | Method
->> 439 | processError in oracle.jdbc.driver.T4CTTIoer
- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
| 395 | processError in ''
| 802 | processError . . . . in oracle.jdbc.driver.T4C8Oall
| 436 | receive in oracle.jdbc.driver.T4CTTIfun
| 186 | doRPC . . . . . . . in ''
| 521 | doOALL in oracle.jdbc.driver.T4C8Oall
| 205 | doOall8 . . . . . . in oracle.jdbc.driver.T4CPreparedStatement
| 1008 | executeForRows in ''
| 1307 | doExecuteWithTimeout in oracle.jdbc.driver.OracleStatement
| 3449 | executeInternal in oracle.jdbc.driver.OraclePreparedStatement
| 3530 | executeUpdate . . . in ''
| 1350 | executeUpdate in oracle.jdbc.driver.OraclePreparedStatementWrapper
| 16 | selection . . . . . in investigator.StatusReportsController
| 198 | doFilter in grails.plugin.cache.web.filter.PageFragmentCachingFilter
| 63 | doFilter . . . . . . in grails.plugin.cache.web.filter.AbstractFilter
| 1145 | runWorker in java.util.concurrent.ThreadPoolExecutor
| 615 | run . . . . . . . . in java.util.concurrent.ThreadPoolExecutor$Worker
^ 745 | run in java.lang.Thread
I solved my own issue.
There was a Date field in the table that was misrepresented as a String in the Domain class. Even though I was trying to update a VARCHAR2 field the domain_class.save() method updates every attribute of that domain_class-- thus the error was not associated with the TREND field but the misrepresented DATE field.
I figured this out by adding to DataSource.groovy
logSql = true
formatSql = true
and Config.groovy
log4j.main = {
trace 'org.hibernate.type'
debug 'org.hibernate.SQL'
...
This showed that the update statement generated by .save() tries to update EVERY attribute.
Hope this helps someone in the future.