This Documentation is for anyone who wants to develop for the Smoca platform. Whether you're working on the framework or just writing tests, this is for you.

### Table of Contents
- [Preparing your Environment](#preparing-your-environment)
- [Writing Tests](#writing-tests)
- [Code Reivew](#code-review)

# Preparing your Environment

### Prelude
If you're a new engineer, be sure you have completed the steps in the [New Engineer Guide](https://git-aws.internal.justin.tv/twitch/docs/blob/master/spinup/new.md#development-environment) before continuing.

## Tools

##### Xcode

Install [Xcode command line tools](https://developer.apple.com/downloads/index.action).

##### Homebrew

Follow instructions at http://brew.sh/, copied below.

```shell
ruby -e "$(curl -fsSL https://raw.githubusercontent.com/Homebrew/install/master/install)"
brew doctor
brew update && brew upgrade
```

Edit your path to prefer `/usr/local/bin` if it doesn't already by adding:

```shell
export PATH="/usr/local/bin":$PATH
```

to your `~/.bash_profile`. Alternatively: `echo 'export PATH="/usr/local/bin":$PATH' >> ~/.bash_profile`

##### Git

At the time of writing, the version of Git that came with OSX had a major [security vulnerability](https://github.com/blog/1938-vulnerability-announced-update-your-git-clients).

```shell
brew install git
```

##### Rbenv and ruby-build

```shell
brew install rbenv ruby-build
```

Add rbenv eval to your `~/.bash_profile`

```shell
echo 'eval "$(rbenv init -)"' >> ~/.bash_profile
```

Source your profile

```shell
source ~/.bash_profile
```

## Get Code, Ruby and Bundle Dependencies

#### Clone qa/smoca.

It's recommended if you're just joining Twitch, to have a folder for all Twitch repositories. You can also create directories per organization.

Example:
```
mkdir -p ~/twitch/qa
cd ~/twitch/qa
git clone git@git-aws.internal.justin.tv:qa/smoca.git
cd smoca
```

Your folder structure will look like:
`~/twitch/qa/smoca`

#### Install Ruby
We are currently on 2.3.1.

*NOTE:* You shouldn't run any of these commands with `sudo`. If you do, the following steps will break. Make sure to run these commands from within your smoca directory.

```shell
rbenv install `cat .ruby-version`
rbenv rehash
gem update --system
gem install bundler -v 1.11.2
rbenv rehash
```

#### Install Smoca's Gems
Gems are packages that Smoca relies on.

`bundle install`

NOTE: `bundle install` should not be run with `sudo`. If something requires elevated permissions, it's misconfigured.

## Install Firefox

Smoca runs by default on Mozilla Firefox. It must be on your system for Smoca to be able to run.

Install Firefox [45.7.0esr](https://ftp.mozilla.org/pub/firefox/releases/45.7.0esr/mac/en-US/).

**You must run a Firefox version less than 48.** Smoca does not support the latest version of Firefox. Follow progress on supporting latest versions of Firefox [here](https://twitchtv.atlassian.net/browse/SMOC-469).

#### Using Non-System Default Firefox
As a developer, you may want to use the latest Firefox version as the system default, but specify Smoca to use 45 ESR.

You can specify `SMOCA_FIREFOX_PATH` in your `~/.bash_profile` to point to a custom binary path for Smoca to use.

Example:

1. Download Firefox 45 ESR using the link in the section above
2. Move `Firefox.app` to `~/twitch/qa/smoca` instead of in `/Applications`
3. Configure your `SMOCA_FIREFOX_PATH` (below)

Add to your `.bash_profile`
```shell
export SMOCA_FIREFOX_PATH=~/twitch/qa/smoca/Firefox.app/Contents/MacOS/firefox-bin
```

Alternatively, run in your terminal:
```shell
echo 'export SMOCA_FIREFOX_PATH=~/twitch/qa/smoca/Firefox.app/Contents/MacOS/firefox-bin' >> ~/.bash_profile
```

These paths can be customize to wherever you'd prefer your Firefox 45 version.

Then, before running Smoca, run:
```shell
source ~/.bash_profile
```

#### Amazon ACME Compliance
If ACME informs you that you must update Firefox, you can open Firefox's auto-update, which will automatically update you to the latest 45ESR version.

Amazon supports older versions as long as they are ESR.

# Running Tests

See the [main documentation](../../README.md#locally) for running tests via the command line.

# Writing Tests
Smoca is written in [Ruby](https://www.ruby-lang.org/en/), utilizing [RSpec](http://rspec.info/), [Capybara](https://github.com/jnicklas/capybara) & [Selenium Webdriver](http://www.seleniumhq.org/projects/webdriver/).

### Capybara Syntax

In most cases, you'll never actually directly talk to the Selenium Webdriver. Capybara comes with a very intuitive DSL which is used to express actions that will be executed.

#### Common methods you may use include:
- `visit('link')`
- `click_on('Link Text')`
- `fill_in('Title', :with => 'Text')`
- `page.has_content?('Text')`

#### as well as expectations:
- `expect(page).to have_content('text')`
- `expect(page.to have_css('.css')`
- `expect(page).not_to have_css('.css')`


More examples can be found in the [resources section](#resources).


### Template

To be read by RSpec, files should end in `_spec.rb`, such as `signup_spec.rb`. They should be placed within the `spec` folder.

```
require './core/base/spec_helper'

feature 'User is able to see' do
  scenario "front page carousel has 6 members" do
    expect(page).to have_css('.js-carousel-nav', :count => 6)
  end

  scenario 'front page featured games section has 12 members' do
    expect(page).to have_css('.game', :minimum => 10)
  end
end

feature 'User is able to click on' do
  scenario 'a channel' do
    visit '/directory'
    first('a.cap').click
  end
end
```

The above code demonstrates you can split things off by Features, and then get more specific with scenarios. You can have as many features or scenarios as you'd like.
The Feature Spec format is described [here](https://www.relishapp.com/rspec/rspec-rails/docs/feature-specs/feature-spec).

### Style Tips

CSS References should be made at the top of the file or top of the feature.

You should reduce the amount of CSS you hard-code into the actual Capybara executions.

In a scenario like:
```
 scenario 'can display a notification menu on request' do
    visit('/qa_partner')

    expect(page).to have_css('.toggle-notification-menu.js-toggle-notification-menu)
    expect(page).not_to have_css('.dropmenu.notify-menu.js-notify') # Prompt not visible before clicking

    find('.toggle-notification-menu.js-toggle-notification-menu').click
    expect(page).to have_css('.toggle-notification-menu.js-toggle-notification-menu')
    expect(page).to have_css('.dropmenu.notify-menu.js-notify') # Prompt is now visible
  end
```

We can see that `'.toggle-notification-menu.js-toggle-notification-menu'` and `'.dropmenu.notify-menu.js-notify'` are repeated often. A CSS class name change (which can be often) becomes cumbersome to maintain.

You can change it to:
```
follow_button_css = '.follow-button.is-initialized'
toggle_notification_prompt_css = '.toggle-notification-menu.js-toggle-notification-menu'
notification_prompt_css = '.dropmenu.notify-menu.js-notify'

 scenario 'can display a notification menu on request' do
    visit('/qa_partner')

    expect(page).to have_css(toggle_notification_prompt_css)
    expect(page).not_to have_css(notification_prompt_css) # Prompt not visible before clicking

    find(toggle_notification_prompt_css).click
    expect(page).to have_css(toggle_notification_prompt_css)
    expect(page).to have_css(notification_prompt_css) # Prompt is now visible
  end
```

### Specifying Browser Support

Examples can specify browser names that are unsupported, due to the nature of differences between the browser drivers.

Within an example's metadata, you can provide an array of browser symbols: `:unsupported_browsers => [:browser_name]`

Example:
```
scenario 'from the front page', :unsupported_browsers => [:edge, :firefox] do
```

### User Accounts

When logging into user accounts, or accessing/manipulating data, it is important to register these with the Smoca framework.

We have created tooling that will help allow us to scale concurrently with writing data that won't conflict.

#### Basic Definitions
- **UserType**
  - A root classification that can hold multiple accounts.
  - For example, if you want a partner account, it can be qa_auto_partner
- **UserName**
  - The actual Twitch account. They belong to a UserType.
  - For example, you can have "qa_auto_partner1" & "qa_auto_partner2" assigned to the above type
    - A call to register_user 'qa_auto_partner' can return either of the above usernames

You'll ask Heimdall for a UserType, in which it will return an available username. This helps us scale.

#### Existing accounts

An existing account is defined as an account already declared in [user_data.yaml](/core/data/user_data.yml).

Within the Feature level of a block (not scenario or before/after), define an account using `register_user('my_user_type')`.

Example:
```
feature 'My feature' do
  my_account = register_user('my_user_type')
  
  scenario 'my scenario' do
    puts "This is my username: #{my_account.user.username}"
  end
end
```

`register_user` will register the type of a user you are looking for, [UserType](/core/data/user_type.rb).
This may return a different username, like `my_account1`, `my_account2`, etc as explained in [Basic Definitions](#basic-definitions).

To access the username you were assigned, call `.user` on your variable.
This will allow you to access the [UserData](/core/data/user_data.rb) object.

#### New account

You'll want to register a new User Type or User Account if it doesn't yet exist.

1) Add each account credential to the bottom of [user_data.yaml](/core/data/user_data.yml). Email and Password are required.
2) Add the account(s) to Heimdall via the [below instructions](#adding-accounts-to-heimdall).
3) Follow the steps in [Registering an existing account](#registering-an-existing-account) to access usernames of that type.

#### Adding Accounts to Heimdall

We've created a script to add UserTypes and UserNames to Heimdall. Run it via:

`bundle exec ruby resources/scripts/add_heimdall_user.rb`

If you're adding a new UserType, provide a description of what that account can be used for. Example:

http://qe-heimdall.us-west2.justin.tv/types/qa_auto_partner/

If you're adding a new username, if it is not yet available on staging, enter "y" when it asks to lock on staging.

Alternatively, you can manually make post requests to:
- https://qe-heimdall.us-west2.justin.tv/types
- https://qe-heimdall.us-west2.justin.tv/types/<INPUT_TYPE_NAME>/users

#### Arguments

There are arguments you can provide to `register_user`.
Refer to the documentation for register_user within [UserUtils](/core/utils/user_utils.rb).

### Logging

Adding logging to your methods and tests can help debug issues. Instead of adding `puts` statements, consider using our logger.

#### Writing to Logger
Logging is available in all objects that require:

- `spec_helper.rb`
- `page_helper.rb`

If not within a file that requires any of the above, you can include it with:
```
require './core/utils/logger_utils.rb'

include LoggerUtils
```

To access the logger object, simply type `logger`

Available log levels:

- `logger.debug`
- `logger.info`
- `logger.warn`
- `logger.error`
- `logger.fatal`
- `logger.unknown`

Example:
```
expression = 2 + 2
logger.info "The expression result is #{expression}

if expression < 13
  logger.error 'You must be 13 years or older to use this logger'
end

```

More information: https://ruby-doc.org/stdlib-2.1.0/libdoc/logger/rdoc/Logger.html

#### Changing Log Level
The default log level is: Error

To change the level of logs printed, you can pass in the environment variable `SMOCA_LOG_LEVEL`

Example:
`SMOCA_LOG_LEVEL=info bundle exec rake`

This is defined within spec_helper.rb

### Runtime Logs
To optimize our parallel run-times, we split up each parallel thread based on reported average run-time duration.
That way, each thread is assigned specs, and all threads should complete around the same time.

Average durations are recorded in:

- [tier1_runtime.log](/resources/logs/tier1_runtime.log)
- [tier2_runtime.log](/resources/logs/tier2_runtime.log)

When adding a new spec, or modifying an existing spec extensively, you should update the runtime logs.

It's recommended to run your individual spec with `TIER2=true` (tier2_runtime.log) and `TIER2=false` (tier1_runtime.log)
5 times, and take the average of the durations.

Example:
- `TIER2=true bundle exec rspec spec/web/front_page_spec.rb`
  - Run 5 times and report time to [tier2_runtime.log](/resources/logs/tier2_runtime.log) in seconds
- `TIER2=false bundle exec rspec spec/web/front_page_spec.rb`
  - Run 5 times and report time to [tier1_runtime.log](/resources/logs/tier1_runtime.log) in seconds

A dynamic method of automatically doing this is pending development.

### Method Documentation

While we currently don't have a server to display method documentation, we recommend you prepare your methods for such ability.

When adding new methods, add comments above the definition that explains the use cases.

You should also document all parameters and return values.

- http://www.rubydoc.info/gems/yard/file/docs/Tags.md#param
- http://www.rubydoc.info/gems/yard/file/docs/Tags.md#return
- http://www.rubydoc.info/gems/yard/file/docs/Tags.md#option

### Return Types

It's recommended in Smoca to always use the `return` keyword when intending to return a value.

While Ruby will return the last evaluated line, this reduces the likelihood of someone modifying a method
and adding something beneath the value you intended to return.

### Resources
- [Capybara DSL](https://github.com/jnicklas/capybara#The_DSL)
- [Capybara Cheatsheet](https://upcase.com/test-driven-rails-resources/capybara.pdf)
- [RSpec Matchers](https://www.relishapp.com/rspec/rspec-expectations/docs/built-in-matchers)
- [Better Specs - RSpec Guidelines with Ruby](http://betterspecs.org/)

## Code Review

It's vital your code be reviewed.

We use both Phabricator and Github. If you have a particular person in mind, you can use [Phabricator](https://phabricator.internal.justin.tv/). If you don't know who should CR your code, just open a Pull Request on Github.

#### Phabricator

##### Arcanist Configuration

Arcanist (`arc`) is a command-line interface to Phabricator, our code review tool. Before you can run the last command, 'install-certificate', you'll need to clone the web project and be in that directory.

```shell
cd ~/twitch/smoca
brew install --HEAD homebrew/php/arcanist
arc install-certificate
```

Follow the steps given by the last command, and log in with your LDAP account when it asks you to.

##### Using Sublime with Arcanist

If you have Sublime Text installed, then you can use it to edit your messages that `arc diff` requires.  In order to do this, Sublime Text will need to be executable from the command-line.  To check if this is so, simply run `which subl`.

If a path didn't return, then you can create a symlink to your Sublime executable to allow it be ran from the command-line.  Simply run the following:

```
ln -sf /usr/local/bin/subl $path_to_sublime
```

Where *$path_to_sublime* is the path to where Sublime is installed on your filesystem.  So, for example, on my machine it looks like this:

```
ln -sf /usr/local/bin/subl /Applications/Sublime\ Text.app/Contents/SharedSupport/bin/subl
```

Once pathed, run `arc set-config editor '/usr/local/bin/subl -w -n'` to hook it up with arc.

##### Opening a CR

Run: `arc diff origin/master`. Type a solid summary of what you're doing, and a test plan that covers how to test it, and if you already tested it (**you should have tested it locally!**)
