Monday, May 30, 2016

Ruby/Cucumber and vim in multi-project environments...

I'm currently working as a Senior QA Automation Engineer for a major bricks-and-mortar retailer that also has a large online presence. They offer a mobile app, a mobile-website experience, and special support for tablet, Android, and iOS devices. Needless to say, there are a number of different top-level projects, each providing a similar but customized experience for the channel their customers use.

QA automation (QAA) uses ruby/cucumber for automated testing, and the QAA environment provides the 'standard' cucumber structure with features/, features/step_defintions/ for feature files and step definitions. Each top-level project has their own sub-directories so, for example, the mobile-website project uses:
features/mobweb/
features/step_definitions/mobweb
while the tablet project uses:
features/tablet/
features/step_definitions/tablet/
I use vim/tmux for my IDE and depend on vim-cucumber (thanks to Tim Pope for a great plugin) to find relevant step definitions from the feature files. The problem I ran into is that -- because of the multiple project sub-folders -- I was constantly getting the error: "Multiple matching steps found" and thus had to examine each alternative step individually to find the one that related to my project.

This was seriously irritating (not to mention time-consuming) so this weekend I dug into the vim-cucumber code to see what could be done. It turned out to be pretty straight-forward (see my fork of vim-cumber here; I've submitted a pull request and hope it gets merged).

Tim's approach is to first find the top-level cucumber feature file directory (this is in the ftplugin/cucumber.vim file); that could be either features/ or stories/.
let b:cucumber_root = expand('%:p:h:s?.*[\/]\%(features\|stories\)\zs[\/].*??')
That is then saved as a globbable name (e.g., features/**/*.rb):
let b:cucumber_steps_glob = b:cucumber_root.'/**/*.rb'
and later used to find all the relevant ruby files through the use of vim's glob statement:
for file in split(glob(b:cucumber_steps_glob),"\n")
and then iterate through the list looking for the step you are searching for. Nice stuff.

Of course, with the multi-project directory structure we have, there are many 'duplicate' step definitions: we're all tapping on buttons or checking the status of similar things. Think
When(/^I tap on the "(.*)" button$/) do... 
Then(/^I should see the "(.*)" button is (enabled|disabled)$/) do...
as simple examples).

I now have a working solution with a small footprint on the basic plugin. I looked at different ways of doing this, but then hit upon this solution. Export a glob spec to the shell environment variable CUKEFILES. When setting the glob spec, vim-cucumber looks for the existence of this variable and uses it instead of the default value.
if !exists("b:cucumber_steps_glob")
  if empty($CUKEFILES)
    echom "Using default definition for b:cucumber_steps_glob"
    let b:cucumber_steps_glob = b:cucumber_root.'/**/*.rb'
  else
    echom 'Using CUKEFILES environment variable for b:cucumber_steps_glob'
    let b:cucumber_steps_glob = $CUKEFILES
  endif
endif
Once that's set, everything works just as it did before, except with a more precise set of files. Note the slight difference when using CUKEFILES: it assumes that the environment variable already has the /**/*.rb set, so that you could define multiple directories in your glob spec.

The CUKEFILES setting I'm using is:
export CUKEFILES=./features/step_definitions/mobweb/**/*.rb
Now my multiple matches truly indicate duplicate step definitions that I need to examine and probably correct.

Finding a working glob statement was it's own challenge. I wanted to have the option of including more than one directory in the glob spec by using the "|" separator but that's another story

Sunday, May 22, 2016

ES6 highlighting and lint checking in vim

I am a long-time "vimmie" (that is, I use vim for my IDE) and I love the plugin ecosystem which lets me do just about anything I want.

For example, I recently started using ReactJS (wish I hadn't wasted to so much time trying to get Angular to work) and one thing I love about it is how straight-forward it is to setup and start writing TDD/BDD for development (but that's another story).  My configuration uses Babel and es6, so I wanted to get syntax checking and indentation/highlighting working right off the bat.

Here's how -- it's pretty easy. I'm using Vundle as my plugin manager so that's the examples given. If you're using pathogen or vim-plug as your plugin manager, it should be pretty easy to modify and get it running. I've used pathogen and it's excellent, but have no experience with vim-plug.

  1. If this is the first time you've used a plugin, go to the Vundle website and follow the instructions there.
  2. Install the vim-javascript plugin by pangloss: vim-pangloss. This will give you basic syntax-highlighting / indentation using vim's built-in syntax support.
  3. Install the syntastic plugin by scrooloose: syntastic (he's the author of the excellent NERDTree plugin which gives you a hierarchical tree menu for system files). This will allow you to use external syntax checkers with vim, in addition to the built-in syntax checking already available. I use the eslint program for this. The options I use in vim for syntastic are:
    """"""""""""""""""""" Syntastic """"""""""""""""""""""""
    " From: http://usevim.com/2016/03/07/linting/
    set statusline+=%#warningmsg#
    set statusline+=%{SyntasticStatuslineFlag()}
    set statusline+=%*
    let g:syntastic_always_populate_loc_list = 1
    let g:syntastic_loc_list_height = 5
    let g:syntastic_auto_loc_list = 0
    let g:syntastic_check_on_open = 1
    let g:syntastic_check_on_wq = 1
    let g:syntastic_javascript_checkers = ['eslint']
    let g:syntastic_error_symbol = '❌'
    let g:syntastic_style_error_symbol = '⁉️'
    let g:syntastic_warning_symbol = '⚠️'
    let g:syntastic_style_warning_symbol = '💩'
    highlight link SyntasticErrorSign SignColumn
    highlight link SyntasticWarningSign SignColumn
    highlight link SyntasticStyleErrorSign SignColumn
    highlight link SyntasticStyleWarningSign SignColumn
    """""""""""""""""""""""""""""""""""""""""""""""""""""""
    
  4. Install the eslint program. This is a node module installed with the 'global' option so that it's available everywhere as an executable. This is just
    npm install -g eslint
    
  5. You would think this would work (I sure did) but it's not yet fully baked. With this setup, the first import statement in some of your .js files will be flagged as an error. To solve this issue, you have to give eslint a few parameters, in the file ~/.eslintrc.json
    // From: http://eslint.org/docs/user-guide/configuring
    {
        "parserOptions": {
            "ecmaVersion": 6,
            "sourceType": "module",
            "ecmaFeatures": {
                "jsx": true
            }
        },
        "rules": {
            "semi": 2
        }
    }
    
Now you've got full es6 syntax checking and error notifications.