Skip to main content

JS Arrow Functions

Since v3.56.0

You can execute JS functions in Gwen using the arrow syntax.

Zero argument expressions

These accept no arguments and have either:

  • a single expression in the body which returns a value
  • a block containing zero or more statements, followed by a return expression
  Given today is defined by js "() => new Date().toISOString().substring(0, 10)"
    And tomorrow is defined by js
"""
() => {
const today = new Date()
const tomorrow = new Date()
tomorrow.setDate(today.getDate() + 1)
return tomorrow.toISOString().substring(0, 10)
}
"""

Prior to arrow functions being introduced, the equivalent of the above was:

    And tomorrow is defined by js
"""
(function() {
const today = new Date()
const tomorrow = new Date()
tomorrow.setDate(today.getDate() + 1)
return tomorrow.toISOString().substring(0, 10)
})()
"""

Single argument functions

These accept a single argument and have either:

  • a single expression that operates on the argument in the body and returns a value
  • a block containing zero or more statements that operate on the argument, followed by a return expression

Implicit argument binding

An argument is implicity bound to a same named reference in scope. In the following example, the argument name on line 2 takes on the value of the name reference one line 1 which sources its text value from a web element.

  Given name can be located by css ".name"
And uppercased name is defined by js "(name) => name.toUpperCase()"

Prior to arrow functions being introduced, the equivalent of the above was:

    And uppercased name is defined by js "'${name}'.toUpperCase()"

So if name contained a single quote in it such as O'Reilly for example, the above would have resolved to 'O'Reilly'.toUpperCase(), which would then result in a missing closed quote error at runtime. With arrow functions, this would never happen since the value is bound to the argument name and not a quoted literal. So arrow functions are much safer in this regard.

Explicit argument binding

When references have spaces in their names, they cannot be implicitly bound to function arguments in the same manner as above (since JavaScript does not permit spaces in argument names). The solution is to explicitly assign the reference to the argument using the = operator. Here, the argument name on line 4 takes on the value of the first name reference on line 1 which sources its text value from a web element.

    Given first name can be located by css ".first_name"
And proper name is defined by js
"""
(name = first name) => {
const head = name.charAt(0).toUpperCase()
const tail = name.slice(1).toLowerCase()
return head + tail
}
"""

Dynamic argument binding

Suppose we wanted to reuse the above function to captitalise several names? The first step would be to redefine it without explicitly binding the name argument to any reference as follows:

    Given capitalise is defined by js
"""
(name) => {
const head = name.charAt(0).toUpperCase()
const tail = name.slice(1).toLowerCase()
return head + tail
}
"""

We can then apply it to different names by binding each one. In the following example, line 3 binds the name argument to the value returned by first name, applies our capitalise function (above) to that and stores the resulting value in proper first name. Line 4 does the same but with last name instead.

  Given first name can be located by css ".first_name"
And last name can be located by css ".last_name"
And proper first name is defined by capitalise applied to "${first name}"
And proper last name is defined by capitalise applied to "${last name}"

Multi argument functions

These accept multiple arguments and have either:

  • a single expression that operates on the arguments in the body and returns a value
  • a block containing zero or more statements that operate on the arguments, followed by a return expression

Implicit argument binding

Arguments are implicity bound to same named references. In this example, the arguments name and surname on line 3 take on the resolved values of the name and surname references on line 1 and 2 respecitvely, which each source their text values from two different web elements.

  Given name can be located by css ".name"
And surname can be located by css ".surname"
And full name is defined by js "(name, surname) => name + ' ' + surname"

Explicit argument binding

Here, the arguments name and surname on line 5 take on the resolved values of the first name and last name references on lines 1 and 2, which source their text values from two different web elements.

    Given first name can be located by css ".first_name"
And last name can be located by css ".last_name"
And full name is defined by js
"""
(name = first name, surname = last name) => name + ' ' + surname
"""

Dynamic argument binding

Suppose we wanted to reuse the above functions to captitalise both the first and last names and combine them into a proper full name?

First, we capitalise the names by reusing the capitalise function we defined earlier above:

  Given capitalise is defined by js
"""
(name) => {
const head = name.charAt(0).toUpperCase()
const tail = name.slice(1).toLowerCase()
return head + tail
}
"""
And first name can be located by css ".first_name"
And last name can be located by css ".last_name"
And proper first name is defined by capitalise applied to "${first name}"
And proper last name is defined by capitalise applied to "${last name}"

Then we reuse and apply the first full name function we defined earlier to combine the two:

  And full name is defined by js "(name, surname) => name + ' ' + surname"
And proper full name is defined by full name applied to "${proper first name},${proper last name}" delimited by ","

Prior to arrow functions being introduced, the full name function needed to be defined like this:

  And full name is defined by js "(function(name, surname) { return name + ' ' + surname })(arguments[0], arguments[1])"

The arrow function equivalent is much cleaner and simpler:

  And full name is defined by js "(name, surname) => name + ' ' + surname"

Inlined functions

Functions can be used in placehoder references inline.

Expressions

Consider the following which uses the @Eager annotation to immediately evaluate a function and store the result in uppercased name:

  Given name can be located by css ".name"
And @Eager uppercased name is defined by js " name => name.toUpperCase() "

Another way to do this could be to declare the function inline as follows:

    And uppercased name is "${ name => name.toUpperCase() }"

Functions can be inlined in this manner wherever placehoders are used. When used in this way, they evaluate immediately and cannot be reused elsewhere.

Blocks

A more complex example follows. If name resolves to 'GWEN' and surname resolves to 'Butterfly', then the following function would set fancy full name to 'BUFFERFLY, Gwen'.

  Given name can be located by css ".name"
And surname can be located by css ".surname"
And fancy full name is
"""
${
(name, surname) => {
const firstName = name.charAt(0).toUpperCase() + name.slice(1).toLowerCase()
const lastName = surname.toUpperCase()
return lastName + ', ' + firstName
}
}
"""

Nested

Inlined functions can also be nested. The following example reads a number from a web page, pads it with leading zeroes and then uses that to reference a value configured in another binding. In this example, if the number field on a web page contains 1, then username will be assigned to the username bound to user.001.username (which could be a configured in a setting for example).

  Given number can be located by css ".number"
And username is "${user.${ number => ('00' + number).slice(-3) }.username}"

Nesting should be used sparingly and would require the following annotation to pass a dry run (since JS functions cannot be evaluated in dry runs):

  And username is    @DryRun(name=`number => ('00' + number).slice(-3)`,value='001')
"""
${user.${ number => ('00' + number).slice(-3) }.username}
"""

REPL Support

You can also exeucte arrow functions directly in the REPL console.

   __ ___      _____ _ __     _    
/ _` \ \ /\ / / _ \ '_ \ { \,"
| (_| |\ V V / __/ | | | {_`/
\__, | \_/\_/ \___|_| |_| `
|___/

Welcome to gwen-web v3.56.0
gweninterpreter.org


REPL Console

Enter steps to evaluate or type help for more options..

gwen> Given name is "Gwen"

[10ms] ✔

gwen> name => name.toUpperCase()

'GWEN'

gwen>