CycleScript coding conventions

CycleScript coding conventions

Objective

This document outlines the conventions for developing consistent, readable, and maintainable CycleScript code. 

Philosophy

  1. Ensure readability for technical and non-technical users.
  2. Validate state before action.
  3. Use modularity to prevent code repetition.
  4. Convention over configuration.
  5. Adhere to the API Mandate.

What do those mean?

  1. CycleScript is designed to be a common language between business stakeholders and technical engineers. In order to ensure this, CycleScript should not be written like a programming language, but like a naturally readable standard operating procedure using business and comment steps.
    1. Here’s an example of harder to read code:
      Given I assign 0 to variable "x"
      While I verify number $x is less than 5
      Then I execute scenario "Unload the Truck"
      And I increase variable "x" by 1
      EndWhile
    1. The previous example lacked indentation, didn’t have a step to describe the goal of the code chunk, and used ambiguous variable names. Here’s an example of easier to read code:
      Given I "unload the truck five times"
          Given I assign 1 to variable "times_unloaded"
          While I verify number $times_unloaded is not equal to 5
              Then I execute scenario "Unload the Truck"
              And I increase variable "times_unloaded" by 1
          EndWhile
  1. Before you perform any action in CycleScript, first validate that you are where you want to be - whether that’s a web page, terminal screen, or application. Confounding variables such as system lag, application errors, and unexpected behavior can affect state and therefore break your action.
    1. Use steps with a within clause to validate state while accounting for variable response times.
    2. Use an I see cursor step in the terminal before interacting with it to ensure the terminal screen has fully rendered.
    3. Use an I see element step in the web before interacting with the element to ensure the web page has rendered correctly and entirely.
  2. Within reason, extract code duplication to their own scenarios, so that maintenance of that logical component is centralized. This keeps code clean by reducing repetition.
    1. Here’s an example of WET pseudo-code:
      Scenario: Plan Shipment from Order
      Given I "obtain the Order Number"
          Given I "obtain a dynamic value from the screen I am on"
          When I "build a custom query by concatenating that dynamic value"
          And I "query the appropriate data source for the order number"

      Then I "plan shipment from order"
          Given I "navigate to the correct screen"
          And I "open the shipment planning modal"
          When I "select the order"
          And I "plan the shipment"
          Then I "verify the shipment is planned"

      Scenario: Allocate Shipment
      Given I "obtain the Order Number"
          Given I "obtain a dynamic value from the screen I am on"
          When I "build a custom query by concatenating that dynamic value"
          And I "query the appropriate data source for the order number"
         
      Then I "allocate shipment"
          Given I "navigate to the correct screen"
          And I "open the allocation modal"
          When I "filter by the order number"
          And I "select the shipment"
          And I "allocate it"
          Then I "verify the shipment is allocated"
    1. The functionality to obtain the order number can be extracted. Here’s an example of DRY pseudo-code:
      Scenario: Plan Shipment from Order
      Then I "plan shipment from order"
          Given I "navigate to the correct screen"
          And I "open the shipment planning modal"
          When I "select the order"
          And I "plan the shipment"
          Then I "verify the shipment is planned"

      Scenario: Allocate Shipment
      Then I "allocate shipment"
          Given I "navigate to the correct screen"
          And I "open the allocation modal"
          When I "filter by the order number"
          And I "select the shipment"
          And I "allocate it"
          Then I "verify the shipment is allocated"

      Scenario: Obtain Order Number
      Given I "obtain the Order Number"
          Given I "obtain a dynamic value from the screen I am on"
          When I "build a custom query by concatenating that dynamic value"
          And I "query the appropriate data source for the order number"
  1. Rather than reinventing the wheel for standard implementations and practices, stay consistent with existing CycleScript and conventions.
    1. Here’s an example of inconsistent coding practices:
      @wip @receive-pallet
      Scenario: Receive Pallet
      # DESCRIPTION: THIS CASE RECEIVES A PALLET
      # INPUT: SPECIFY THE LODNUM TO RECEIVE
      # OUTPUT: THE LODNUM WILL BE RECEIVED
      Given I "navigate to the receiving terminal screen"
          When I "navigate there"
          And I "type menu options"

      And I "receive the pallet"
          When I "type in the lodnum"
          And I "perform countback"
          And I "deposit it"

      @wip @receive_pallet
      ################################################
      # Scenario: Receive Case
      # Description:  This scenario, given a load
      #               number, will start at the
      #               Undirected Menu and go receive
      #               the case.
      # Input:  $lodnum
      # Output: N/A.
      ################################################
      Scenario: Receive Case
      Given I "navigate the receiving terminal screen"
        When I "navigate there"
        And I "type menu options"

      And I "receive the pallet"
        When I "type in the lodnum"
        And I "perform countback"
        And I "deposit it"
    2. You can see that both scenarios use different indentation conventions, scenario headers, and tags. This makes code harder to maintain over time - especially as projects scale. Pick one standard for everyone and stick to it.
  2. On a Feature File level, separate public and private scenarios via tags to expose and hide external and internal functionality to other features. This helps convey the developer’s intent and allows the external API to have consistency, while the internal API can change over time with development.
    1. Here’s an abridged example of how to do so:
      Feature: Count Back

      @wip @public
      Scenario: Count Back
      Given I "calculate the remainder"
          Given I execute scenario "Screen Scan"
          When I execute scenario "Calculate Remainder"

      When I "perform Count Back"
          When I execute scenario "Screen Input"

      ###############################
      # Private Scenarios:    
      ###############################
      @wip @private
      Scenario: Screen Scan
      When I "extract the pallets, cases, and eaches listed on the screen"

      @wip @private
      Scenario: Screen Input
      When I "input the pallets, cases, and eaches remaining"

      @wip @private
      Scenario: Calculate Remainder
      Given I execute scenario "Check Database for Picked"
      When I "calculate the remainder in the location"

      @wip @private
      Scenario: Check Database for Picked
      When I "query the database to determine how much was picked"
  3. You can see certain scenarios, below a comment header, are tagged as private. The intent of the developer is that whoever imports this scenario, only the scenario Count Back should be called. This can be done for a variety of reasons, like the private scenarios having internal dependencies.

Feature File structure

In all Cycle projects built on the Base Framework Library, there will be two primary Feature File types - Test Cases and Utilities.
The following sections describe the conventions that these Feature Files adhere to at a high-level.
You may notice that Cycle Labs Test Libraries adhere to additional conventions and include more meta-data than shown below.
Every organization can identify and adopt additional conventions as needed.

Test Cases

There should be a single scenario per Test Case feature file in this format:
###########################################################
# Copyright 20XX, Cycle Labs, Inc.
# All rights reserved.  Proprietary and confidential.
#
# This file is subject to the license terms found at
#
# The methods and techniques described herein are considered
# confidential and/or trade secrets.
# No part of this file may be copied, modified, propagated,
# or distributed except as authorized by the license.
############################################################
Feature: <Test Case>

Background:
Given I "setup the environment"
    Then I assign all chevron variables to unassigned dollar variables
    And I import scenarios from "Utilities/Base/Environment.feature"
    When I execute scenario "Set Up Environment"

    Given I execute scenario "<Import Utility>"

And I "optionally perform test data generation"
   
After Scenario:
Given I "perform test completion activities including logging out of the interfaces"
    Then I execute scenario "Test Completion"

And I "optionally perform test data destruction"

Scenario Outline: <Test Case>
CSV Examples: Test Case Inputs/<Test Case>.csv

Given I "execute pre-test scenario actions (including pre-validations)"
    And I execute scenario "Begin Pre-Test Activities"

Given I "<business_step>"
    When I execute scenario "<scenario>"

And I "execute post-test scenario actions (including post-validations)"
    Then I execute scenario "End Post-Test Activities"

Utilities

There can be as many scenarios in a Utility feature file, although it is recommended to segment utilities into different feature files based on interfaces and functional areas.
All utilities should specify the mandatory and optional inputs in order to allow developers to compose test cases from varied utilities as easily as possible.
Feature: <Utility Name> Utilities

@wip @public
Scenario: <Public Utility Name>
#############################################################
# Inputs:
#   Required:
#       <Variable Name> - <Variable Description>
#   Optional:
#       <Variable Name> - <Variable Description>
# Outputs:
#   <Variable Name> - <Variable Description>
#############################################################

Given I "<Business Step>"
    When I "<Utility implementation>"

@wip @private
Scenario: <Private Utility Name>
#############################################################
# Inputs:
#   Required:
#       <Variable Name> - <Variable Description>
#   Optional:
#       <Variable Name> - <Variable Description>
# Outputs:
#   <Variable Name> - <Variable Description>
#############################################################

Given I "<Business Step>"
    When I "<Utility implementation>"

Naming Conventions

While parsing the project directory and feature files at a glance, these naming conventions are designed for readability - they should speak like natural language.
Conversely, while parsing code, you should be able to glance at a variable and by its convention, deduce whether it is an environment variable.
  1. Folders, Feature Files, and Scenarios use title casing.
    1. Title Casing Tool
    2. Examples:
      Utilities
      Web Services
      Receiving Staging
      Deposit Load
      Find Part for All Shipments
    3. Title Casing - capitalize everything but:
      1. Articles:
        1. a
        2. an
        3. the
      2. Coordinate conjunctions:
        1. for
        2. and
        3. nor
        4. but
        5. or
        6. yet
        7. so
      3. Prepositions: (According to the Chicago Manual of Style)
        1. such as at
        2. around
        3. by
        4. after
        5. along
        6. for
        7. from
        8. of
        9. on
        10. to
        11. with
        12. without
  2. Environment Variables, Variables, and Script Filenames should use lowercase, with words separated by underscores.
    1. Examples:
      short_wait
      loop_count
      generate_lpn.msql

Step Usage

Keywords

Keywords generally should be used as the Cycle User Manual recommends.
As a general rule,
  1. Given is used to verify and transition the application to the intended state - whether that’s the correct web page, terminal screen, importing utilities, and/or collecting the necessary background data - to perform the action.
  2. When performs the actual action - whether that’s clicking a certain button, inputting certain data, and/or performing the primary calculation.
  3. Then validates the result of the action - through a database query, image validation, and/or other mechanisms.
    1. Subsequent to a Business/Activity/Comment Step, the Then keyword can be used if the relevant code chunk is small and would flow more smoothly verbally.
  4. But is used for negative validation.
  5. And is commonly appended to any of the above keywords as a continuation if the task can not be completed in one step.
    1. In the context of a Control Structure, like an If, Else, and While, it acts as a conjoint clause, meaning the condition associated with the And is also calculated alongside the condition associated with the If, Else, and While.
    2. In this example, please note that the And is on the same indentation level as the Control Structure keyword:
      While I "verify that there are still items to receive"
      And I "verify that the items are not damaged"
          Then I "receive an item"
      EndWhile

      If I "am logged into the Terminal"
      And I "am logged in as a handheld device"
          Then I "do handheld work"
      EndIf
  6. Given, When, Then, But, and And should form a logical hierarchy at all times - at their indentation level. You should never use two keywords, other than And, in the same User Story.
    1. Here is an example of two different layers of indentation levels having logical hierarchy:
      Given I "navigate to the Receiving Menu"
          Given I "open the terminal"
          When I "login"
          And I "input the menu option for the Receiving Menu"
          Then I "validate I am at the Receiving Menu"
         
      When I "begin Receiving"
          Given I "query the database for the receiving line"
          When I "input the receiving data"
       
      Then I "validate the Receiving process"
          Then I "validate that the line has been received"
         
      And I "exit the Receiving Menu"
          Given I "return to the Undirected Menu"
          When I "logout of the Terminal"
          Then I "validate I am logged out"
    2. The first layer of indentation has its own logical hierarchy of keywords, and each chunk of the second layer of indentation also has its own logical hierarchy of keywords.
  7. Once should not be used as a keyword because within clauses that use a global environment variable are more easily controllable and achieve the same effect.

Steps

  1. Use in app, in web browser, in element, and in terminal steps instead of generic steps - affix your steps with the most specificity possible.
  2. We advise using I wait as rarely as possible because it is not semantic.
  3. If an I wait step is necessary, such as for a black box background process, precede the step with a business step and treat the I wait step as its own code block.
    Given I "wait for the background process to complete"
        When I wait 10 seconds
  4. Always use I verify screen is done loading in terminal within <NUMBER> seconds after performing an action in the terminal and before a subsequent action.
  5. Use system-specific steps wherever possible, such as using I type option for “<MENU_NAME>” menu in terminal instead of manual number entry in BY WMS.
  6. Use I execute scenario instead of inline scenario calls - scenario calls become more evident with this approach.
  7. Never declare wait times inline - use Environment Variables instead.
  8. If your test uses an inline Groovy, MOCA, or SQL statement such as with I execute SQL "<SQL_STATEMENTS>", I execute MOCA command "<COMMAND_SYNTAX>", and I execute Groovy "<GROOVY_STATEMENTS>":
    1. Move any multi-line logic into a script.
    2. Use Cycle steps instead of external logic if the number of Cycle steps is equivalent to the lines of external logic.
      Here’s an example of what should be a Cycle step:
      # This is a groovy one-liner that should be a Cycle step!
      # This is harder to read for non-technical users and starts a groovy runtime.
      Given I execute groovy "count = count + 1"

      # This is a Cycle one-liner that is the equivalent of the above.
      # This reads inline with CycleScript syntax.
      Given I increase variable "count" by 1

Business Steps

Each logical chunk of CycleScript should be prefixed with a Business Steps.
A Business Step is a non-action step that uses I “<TEXT>” to describe a general action.
These steps should not be indented, and logical code chunks associated to them should be indented directly beneath them.
Here’s an example:
Given I "check the inventory status"
    Given I "obtain the load number"
    And I "open a terminal"
    And I "navigate to the Undirected Menu"
    And I "navigate to the Inventory Menu"
    When I "filter by the load number"
    And I "grab the status from the screen"
    Then I "validate the inventory status"
As shown in the example, the logic associated to the Business Step is indented one level. This allows for CycleScript to map directly to work instructions - you can port work instructions line by line before writing a single functional piece of CycleScript.
There are some cases where the work instruction can have its internal logic of high complexity. In this case, you can indent Business Steps, which use the same I “<TEXT>” step, but on an indented level.
You can think of a Business Step as a programmatic comment to indicate what the subsequent chunk of CycleScript does.

Here is an example:
Given I "send a shipment to a customer"
    Given I "allocate the shipment"
        Given I "navigate to the allocation screen"
        And I "filter for the shipment"
        When I "allocate it"
        Then I "validate it is allocated"
       
    And I "pick the shipment"
        Given I "navigate to the Terminal Picking screen"
        When I "pick all the items"
        Then I "validate it is fully picked"
       
    When I "load the truck"
        Given I "navigate to the Loading screen"
        When I "place all items in the truck"
        Then I "validate all items are in the truck"
       
    Then I "dispatch the truck"
        Given I "handle all safety checks"
        Then I "send the truck off"
The use of Business Steps makes CycleScript much easier to read and therefore more maintainable.
You can use the traditional CycleScript comments to denote other additional information as long as it’s not relevant to the generated report - but valuable to the developers maintaining and developing the code.
# I chose to not include Undirected Putaway because the client
# specified that that should never happen.
# You can expand this branch by adding an extra 'Elsif' on line 32.

Business Steps are not necessary in Backgrounds, After Scenarios, Utilities, and external scenario calls if and only if the associated code blocks are simple and self-evident.

Here is an example of simple and self-evident code:
Given I execute scenario "Allocate Wave"
And I execute scenario "Validate Pick Release"
When I execute scenario "Perform Pallet Picking"
Then I execute scenario "Vaidate Pick Completion"

Business Steps are most critical in Test Case feature files to ensure that the high-level business process flow is easily understandable.
Business Steps should be used as frequently as possible within Utility feature files to ensure that utilities are easily customizable.

Locators

All locators used within steps, such as xPaths, should be selected with the following traits in mind whenever possible:
  1. Unique - avoid using auto-generated IDs - these are prone to be brittle when the system is updated or when a particular screen reloads.
  2. Semantic - use fields that are evident as to what the locator is interacting with.
  3. Concise - use the shortest locator possible.
  4. Relative - avoid absolute locators because these are more prone to be brittle when the system is updated.

SQL

  1. Do not use BEGIN and END statements in any SQL.
  2. Use semicolons as separators when there are multiple SQL statements in a SQL script.

Formatting

These formatting rules are designed to make CycleScript readable and follow the existing conventions that the editor already uses.
Here is a series of rules to follow to ensure written CycleScript maximizes legibility:
  1. Use Forward Slashes for File Paths.
  2. Business Steps should describe and precede an indented code block.
  3. Business Steps and their code blocks should be separated from other Business Steps and their code blocks by a single newline.
  4. Each layer of indentation, excluding While, If, and Else code blocks should have their own logical hierarchy of keywords to form a logical user story.
  5. While, If, Elsif, and Else code blocks should all be indented.
    1. Conjoint And keywords used in conjunction with a Control Structure should be on the same indentation level as the Control Structure keyword.
  6. EndWhile and EndIf should have the second word capitalized to be consistent with Cycle’s auto-fill mechanism.
  7. Scenario Outlines/Examples and their implementation should be separated by a single newline.
    Scenario Outline:
    CSV Examples:

    Given...
    When...
    Then...
  8. Tabs should consist of four spaces - the Cycle editor interprets a tab keystroke as four spaces.
  9. Capitalize keywords.
    Given I "am correct"
    but I "am incorrect and unprofessional looking"
  10. Capitalize the “i” in the step.
    Given I "am correct"
    But i "am incorrect"
  11. Do not leave extra spaces between words in a Cycle step.
    Given I "am correct"
    But      I "             am        incorrect"
  12. Casing of the remainder of the step should be consistent with Cycle’s auto-fill mechanism.
    # One of these is different than the others.
    Then I type option for "Load Receive" menu in terminal
    Then I TYpE oPtION FoR "LOAd ReCeIvE" MENU in TeRmINaL