You are viewing a plain text version of this content. The canonical link for it is here.
Posted to issues@openwhisk.apache.org by GitBox <gi...@apache.org> on 2017/11/15 17:34:16 UTC

[GitHub] csantanapr closed pull request #112: sync-up openwhisk

csantanapr closed pull request #112: sync-up openwhisk
URL: https://github.com/apache/incubator-openwhisk-cli/pull/112
 
 
   

This is a PR merged from a forked repository.
As GitHub hides the original diff on merge, it is displayed below for
the sake of provenance:

As this is a foreign pull request (from a fork), the diff is supplied
below (as it won't show otherwise due to GitHub magic):

diff --git a/Godeps/Godeps.json b/Godeps/Godeps.json
index ca7adaee..10c886e6 100644
--- a/Godeps/Godeps.json
+++ b/Godeps/Godeps.json
@@ -65,7 +65,7 @@
 		},
         {
             "ImportPath": "github.com/apache/incubator-openwhisk-client-go/whisk",
-            "Rev": "ad5aa3402b033edd8b30aec7f33434a253aa35ed"
+            "Rev": "a67e8509a92beb6c68f0c9da43562af1f5d2b13c"
         }
 	]
 }
diff --git a/commands/trigger.go b/commands/trigger.go
index 2e53acdc..52191df9 100644
--- a/commands/trigger.go
+++ b/commands/trigger.go
@@ -32,6 +32,7 @@ const FEED_LIFECYCLE_EVENT  = "lifecycleEvent"
 const FEED_TRIGGER_NAME     = "triggerName"
 const FEED_AUTH_KEY         = "authKey"
 const FEED_CREATE           = "CREATE"
+const FEED_READ             = "READ"
 const FEED_DELETE           = "DELETE"
 
 // triggerCmd represents the trigger command
@@ -294,6 +295,7 @@ var triggerGetCmd = &cobra.Command{
     RunE: func(cmd *cobra.Command, args []string) error {
         var err error
         var field string
+        var fullFeedName string
         var qualifiedName = new(QualifiedName)
 
         if whiskErr := CheckArgs(args, 1, 2, "Trigger get", wski18n.T("A trigger name is required.")); whiskErr != nil {
@@ -326,18 +328,35 @@ var triggerGetCmd = &cobra.Command{
             return werr
         }
 
-        if (Flags.trigger.summary) {
-            printSummary(retTrigger)
+        // Get full feed name from trigger get request as it is needed to get the feed
+        if retTrigger != nil && retTrigger.Annotations != nil {
+            fullFeedName = getValueString(retTrigger.Annotations, "feed")
+        }
+
+        if len(fullFeedName) > 0 {
+            fullTriggerName := fmt.Sprintf("/%s/%s", qualifiedName.GetNamespace(), qualifiedName.GetEntityName())
+            Flags.common.param = append(Flags.common.param, getFormattedJSON(FEED_LIFECYCLE_EVENT, FEED_READ))
+            Flags.common.param = append(Flags.common.param, getFormattedJSON(FEED_TRIGGER_NAME, fullTriggerName))
+            Flags.common.param = append(Flags.common.param, getFormattedJSON(FEED_AUTH_KEY, Client.Config.AuthToken))
+
+            err = configureFeed(qualifiedName.GetEntityName(), fullFeedName)
+            if err != nil {
+                whisk.Debug(whisk.DbgError, "configureFeed(%s, %s) failed: %s\n", qualifiedName.GetEntityName(), fullFeedName, err)
+            }
         } else {
-            if len(field) > 0 {
-                fmt.Fprintf(color.Output, wski18n.T("{{.ok}} got trigger {{.name}}, displaying field {{.field}}\n",
-                    map[string]interface{}{"ok": color.GreenString("ok:"), "name": boldString(qualifiedName.GetEntityName()),
-                    "field": boldString(field)}))
-                printField(retTrigger, field)
+            if (Flags.trigger.summary) {
+                printSummary(retTrigger)
             } else {
-                fmt.Fprintf(color.Output, wski18n.T("{{.ok}} got trigger {{.name}}\n",
-                        map[string]interface{}{"ok": color.GreenString("ok:"), "name": boldString(qualifiedName.GetEntityName())}))
-                printJSON(retTrigger)
+                if len(field) > 0 {
+                    fmt.Fprintf(color.Output, wski18n.T("{{.ok}} got trigger {{.name}}, displaying field {{.field}}\n",
+                        map[string]interface{}{"ok": color.GreenString("ok:"), "name": boldString(qualifiedName.GetEntityName()),
+                        "field": boldString(field)}))
+                    printField(retTrigger, field)
+                } else {
+                    fmt.Fprintf(color.Output, wski18n.T("{{.ok}} got trigger {{.name}}\n",
+                            map[string]interface{}{"ok": color.GreenString("ok:"), "name": boldString(qualifiedName.GetEntityName())}))
+                    printJSON(retTrigger)
+                }
             }
         }
 
diff --git a/commands/util.go b/commands/util.go
index e5ec6ee3..6b84e9ec 100644
--- a/commands/util.go
+++ b/commands/util.go
@@ -39,6 +39,7 @@ import (
     "sort"
     "reflect"
     "bytes"
+    "regexp"
 )
 
 func csvToQualifiedActions(artifacts string) ([]string) {
@@ -1016,6 +1017,9 @@ func ReadProps(path string) (map[string]string, error) {
 
     props = map[string]string{}
     for _, line := range lines {
+        re := regexp.MustCompile("#.*")
+        line = re.ReplaceAllString(line, "")
+        line = strings.TrimSpace(line)
         kv := strings.Split(line, "=")
         if len(kv) != 2 {
             // Invalid format; skip
diff --git a/tests/dat/actions/hello.py b/tests/dat/actions/hello.py
new file mode 100644
index 00000000..d2639d5e
--- /dev/null
+++ b/tests/dat/actions/hello.py
@@ -0,0 +1,27 @@
+"""Python Hello test.
+
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+"""
+
+
+def main(args):
+    """Main."""
+    name = args.get('name', 'stranger')
+    greeting = 'Hello ' + name + '!'
+    print(greeting)
+    return {'greeting': greeting}
diff --git a/tests/dat/actions/hello.swift b/tests/dat/actions/hello.swift
new file mode 100644
index 00000000..7c903a5f
--- /dev/null
+++ b/tests/dat/actions/hello.swift
@@ -0,0 +1,10 @@
+/**
+ * Hello world as a Swift Whisk action.
+ */
+func main(args: [String:Any]) -> [String:Any] {
+    if let name = args["name"] as? String {
+        return [ "greeting" : "Hello \(name)!" ]
+    } else {
+        return [ "greeting" : "Hello stranger!" ]
+    }
+}
diff --git a/tests/dat/actions/helloJava.jar b/tests/dat/actions/helloJava.jar
new file mode 100644
index 00000000..a8f49eec
Binary files /dev/null and b/tests/dat/actions/helloJava.jar differ
diff --git a/tests/dat/actions/helloJavaDefaultPackage.jar b/tests/dat/actions/helloJavaDefaultPackage.jar
new file mode 100644
index 00000000..5a7a9f33
Binary files /dev/null and b/tests/dat/actions/helloJavaDefaultPackage.jar differ
diff --git a/tests/dat/actions/issue-1562.js b/tests/dat/actions/issue-1562.js
new file mode 100644
index 00000000..9064817c
--- /dev/null
+++ b/tests/dat/actions/issue-1562.js
@@ -0,0 +1,6 @@
+// We expect this action to always result in errored activations.
+function main(args) {
+    return new Promise((resolve, reject) => {
+        reject();
+    });
+}
diff --git a/tests/dat/actions/malformed.py b/tests/dat/actions/malformed.py
new file mode 100644
index 00000000..6cedc326
--- /dev/null
+++ b/tests/dat/actions/malformed.py
@@ -0,0 +1,2 @@
+"""Invalid Python comment test."""
+// invalid python comment  # noqa -- tell linters to ignore the intentional syntax error
diff --git a/tests/dat/actions/niam.js b/tests/dat/actions/niam.js
new file mode 100644
index 00000000..378201dc
--- /dev/null
+++ b/tests/dat/actions/niam.js
@@ -0,0 +1,3 @@
+function niam(args) {
+    return { 'greetings': 'Hello from a non-standard entrypoint.' };
+}
diff --git a/tests/dat/actions/niam.py b/tests/dat/actions/niam.py
new file mode 100644
index 00000000..30a49455
--- /dev/null
+++ b/tests/dat/actions/niam.py
@@ -0,0 +1,6 @@
+"""Python Non-standard entry point test."""
+
+
+def niam(args):
+    """Non-standard entry point."""
+    return {"greetings": "Hello from a non-standard entrypoint."}
diff --git a/tests/dat/actions/niam.swift b/tests/dat/actions/niam.swift
new file mode 100644
index 00000000..c85a34c8
--- /dev/null
+++ b/tests/dat/actions/niam.swift
@@ -0,0 +1,4 @@
+/* Swift action with a non-default entry point. */
+func niam(args: [String:Any]) -> [String:Any] {
+    return [ "greetings" : "Hello from a non-standard entrypoint." ]
+}
diff --git a/tests/dat/actions/python2_virtualenv.zip b/tests/dat/actions/python2_virtualenv.zip
new file mode 100644
index 00000000..4808c8a4
Binary files /dev/null and b/tests/dat/actions/python2_virtualenv.zip differ
diff --git a/tests/dat/actions/python3_virtualenv.zip b/tests/dat/actions/python3_virtualenv.zip
new file mode 100644
index 00000000..3f40a150
Binary files /dev/null and b/tests/dat/actions/python3_virtualenv.zip differ
diff --git a/tests/dat/actions/pythonVersion.py b/tests/dat/actions/pythonVersion.py
new file mode 100644
index 00000000..3fc6955a
--- /dev/null
+++ b/tests/dat/actions/pythonVersion.py
@@ -0,0 +1,25 @@
+"""Python Version test.
+
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+"""
+
+import sys
+
+def main(args):
+    """Main."""
+    return {"version": sys.version_info.major}
diff --git a/tests/dat/actions/stdenv.py b/tests/dat/actions/stdenv.py
new file mode 100644
index 00000000..fa39dfdd
--- /dev/null
+++ b/tests/dat/actions/stdenv.py
@@ -0,0 +1,7 @@
+"""Unify action container environments."""
+import os
+
+
+def main(dict):
+    return {"auth": os.environ['__OW_API_KEY'],
+            "edge": os.environ['__OW_API_HOST']}
diff --git a/tests/dat/actions/zippedaction.zip b/tests/dat/actions/zippedaction.zip
new file mode 100644
index 00000000..14cbd4cb
Binary files /dev/null and b/tests/dat/actions/zippedaction.zip differ
diff --git a/tests/src/test/scala/apigw/healthtests/ApiGwCliEndToEndTests.scala b/tests/src/test/scala/apigw/healthtests/ApiGwCliEndToEndTests.scala
new file mode 100644
index 00000000..3025fab2
--- /dev/null
+++ b/tests/src/test/scala/apigw/healthtests/ApiGwCliEndToEndTests.scala
@@ -0,0 +1,33 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package apigw.healthtests
+
+import org.junit.runner.RunWith
+import org.scalatest.junit.JUnitRunner
+
+import common.Wsk
+import common.TestUtils._
+
+/**
+ * Basic tests of the download link for Go CLI binaries
+ */
+@RunWith(classOf[JUnitRunner])
+class ApiGwCliEndToEndTests extends ApiGwEndToEndTests {
+  override lazy val wsk: common.Wsk = new Wsk
+  override val createCode: Int = SUCCESS_EXIT
+}
diff --git a/tests/src/test/scala/apigw/healthtests/ApiGwEndToEndTests.scala b/tests/src/test/scala/apigw/healthtests/ApiGwEndToEndTests.scala
index 909b609f..5cc1ef61 100644
--- a/tests/src/test/scala/apigw/healthtests/ApiGwEndToEndTests.scala
+++ b/tests/src/test/scala/apigw/healthtests/ApiGwEndToEndTests.scala
@@ -34,7 +34,7 @@ import com.jayway.restassured.RestAssured
 import common.TestHelpers
 import common.TestCLIUtils
 import common.TestUtils._
-import common.Wsk
+import common.BaseWsk
 import common.WskProps
 import common.WskTestHelpers
 import spray.json._
@@ -45,7 +45,7 @@ import system.rest.RestUtil
  * Basic tests of the download link for Go CLI binaries
  */
 @RunWith(classOf[JUnitRunner])
-class ApiGwEndToEndTests
+abstract class ApiGwEndToEndTests
     extends FlatSpec
     with Matchers
     with RestUtil
@@ -53,23 +53,58 @@ class ApiGwEndToEndTests
     with WskTestHelpers
     with BeforeAndAfterAll {
 
-  implicit val wskprops = WskProps()
-  val wsk = new Wsk
-  val clinamespace = wsk.namespace.whois()
+  implicit val wskprops: common.WskProps = WskProps()
+  val wsk: BaseWsk
+  val namespace: String = wsk.namespace.whois()
+  val createCode: Int
 
   // Custom CLI properties file
-  val cliWskPropsFile = File.createTempFile("wskprops", ".tmp")
+  val cliWskPropsFile: java.io.File = File.createTempFile("wskprops", ".tmp")
 
   /*
    * Create a CLI properties file for use by the tests
    */
-  override def beforeAll() = {
+  override def beforeAll: Unit = {
     cliWskPropsFile.deleteOnExit()
     val wskprops = WskProps(token = "SOME TOKEN")
     wskprops.writeFile(cliWskPropsFile)
     println(s"wsk temporary props file created here: ${cliWskPropsFile.getCanonicalPath()}")
   }
 
+  def verifyAPICreated(rr: RunResult): Unit = {
+    rr.stdout should include("ok: created API")
+    val apiurl = rr.stdout.split("\n")(1)
+    println(s"apiurl: '$apiurl'")
+  }
+
+  def verifyAPIList(rr: RunResult,
+                    actionName: String,
+                    testurlop: String,
+                    testapiname: String,
+                    testbasepath: String,
+                    testrelpath: String): Unit = {
+    rr.stdout should include("ok: APIs")
+    rr.stdout should include regex (s"$actionName\\s+$testurlop\\s+$testapiname\\s+")
+    rr.stdout should include(testbasepath + testrelpath)
+  }
+
+  def verifyAPISwaggerCreated(rr: RunResult): Unit = {
+    rr.stdout should include("ok: created API")
+  }
+
+  def writeSwaggerFile(rr: RunResult): File = {
+    val swaggerfile = File.createTempFile("api", ".json")
+    swaggerfile.deleteOnExit()
+    val bw = new BufferedWriter(new FileWriter(swaggerfile))
+    bw.write(rr.stdout)
+    bw.close()
+    return swaggerfile
+  }
+
+  def getSwaggerApiUrl(rr: RunResult): String = {
+    return rr.stdout.split("\n")(1)
+  }
+
   behavior of "Wsk api"
 
   it should s"create an API and successfully invoke that API" in {
@@ -83,7 +118,7 @@ class ApiGwEndToEndTests
     val urlqueryvalue = testName
 
     try {
-      println("cli namespace: " + clinamespace)
+      println("Namespace: " + namespace)
 
       // Delete any lingering stale api from previous run that may not have been deleted properly
       wsk.api.delete(
@@ -93,12 +128,14 @@ class ApiGwEndToEndTests
 
       // Create the action for the API.  It must be a "web-action" action.
       val file = TestCLIUtils.getTestActionFilename(s"echo-web-http.js")
+      println("action creation Namespace: " + namespace)
       wsk.action.create(
         name = actionName,
         artifact = Some(file),
-        expectedExitCode = SUCCESS_EXIT,
+        expectedExitCode = createCode,
         annotations = Map("web-export" -> true.toJson))
 
+      println("creation Namespace: " + namespace)
       // Create the API
       var rr = wsk.api.create(
         basepath = Some(testbasepath),
@@ -108,9 +145,7 @@ class ApiGwEndToEndTests
         apiname = Some(testapiname),
         responsetype = Some("http"),
         cliCfgFile = Some(cliWskPropsFile.getCanonicalPath()))
-      rr.stdout should include("ok: created API")
-      val apiurl = rr.stdout.split("\n")(1)
-      println(s"apiurl: '$apiurl'")
+      verifyAPICreated(rr)
 
       // Validate the API was successfully created
       // List result will look like:
@@ -122,17 +157,11 @@ class ApiGwEndToEndTests
         relpath = Some(testrelpath),
         operation = Some(testurlop),
         cliCfgFile = Some(cliWskPropsFile.getCanonicalPath()))
-      rr.stdout should include("ok: APIs")
-      rr.stdout should include regex (s"$actionName\\s+$testurlop\\s+$testapiname\\s+")
-      rr.stdout should include(testbasepath + testrelpath)
+      verifyAPIList(rr, actionName, testurlop, testapiname, testbasepath, testrelpath)
 
       // Recreate the API using a JSON swagger file
       rr = wsk.api.get(basepathOrApiName = Some(testbasepath), cliCfgFile = Some(cliWskPropsFile.getCanonicalPath()))
-      val swaggerfile = File.createTempFile("api", ".json")
-      swaggerfile.deleteOnExit()
-      val bw = new BufferedWriter(new FileWriter(swaggerfile))
-      bw.write(rr.stdout)
-      bw.close()
+      val swaggerfile = writeSwaggerFile(rr)
 
       // Delete API to that it can be recreated again using the generated swagger file
       val deleteApiResult = wsk.api.delete(
@@ -143,8 +172,8 @@ class ApiGwEndToEndTests
       // Create the API again, but use the swagger file this time
       rr = wsk.api
         .create(swagger = Some(swaggerfile.getAbsolutePath()), cliCfgFile = Some(cliWskPropsFile.getCanonicalPath()))
-      rr.stdout should include("ok: created API")
-      val swaggerapiurl = rr.stdout.split("\n")(1)
+      verifyAPISwaggerCreated(rr)
+      val swaggerapiurl = getSwaggerApiUrl(rr)
       println(s"Returned api url: '${swaggerapiurl}'")
 
       // Call the API URL and validate the results
diff --git a/tests/src/test/scala/system/basic/WskActionTests.scala b/tests/src/test/scala/system/basic/WskActionTests.scala
index 519f50bf..b6d35133 100644
--- a/tests/src/test/scala/system/basic/WskActionTests.scala
+++ b/tests/src/test/scala/system/basic/WskActionTests.scala
@@ -24,7 +24,7 @@ import common.ActivationResult
 import common.JsHelpers
 import common.TestHelpers
 import common.TestCLIUtils
-import common.TestUtils
+import common.BaseWsk
 import common.Wsk
 import common.WskProps
 import common.WskTestHelpers
@@ -34,10 +34,10 @@ import spray.json.JsObject
 import spray.json.pimpAny
 
 @RunWith(classOf[JUnitRunner])
-class WskActionTests extends TestHelpers with WskTestHelpers with JsHelpers {
+abstract class WskActionTests extends TestHelpers with WskTestHelpers with JsHelpers {
 
   implicit val wskprops = WskProps()
-  val wsk = new Wsk
+  val wsk: BaseWsk
 
   val testString = "this is a test"
   val testResult = JsObject("count" -> testString.split(" ").length.toJson)
@@ -141,8 +141,8 @@ class WskActionTests extends TestHelpers with WskTestHelpers with JsHelpers {
         action.create(copiedActionName, Some(origActionName), Some("copy"))
       }
 
-      val copiedAction = getJSONFromCLIResponse(wsk.action.get(copiedActionName).stdout)
-      val origAction = getJSONFromCLIResponse(wsk.action.get(copiedActionName).stdout)
+      val copiedAction = getJSONFromResponse(wsk.action.get(copiedActionName).stdout, wsk.isInstanceOf[Wsk])
+      val origAction = getJSONFromResponse(wsk.action.get(copiedActionName).stdout, wsk.isInstanceOf[Wsk])
 
       copiedAction.fields("annotations") shouldBe origAction.fields("annotations")
       copiedAction.fields("parameters") shouldBe origAction.fields("parameters")
@@ -177,10 +177,11 @@ class WskActionTests extends TestHelpers with WskTestHelpers with JsHelpers {
       }
 
       assetHelper.withCleaner(wsk.action, copiedName) { (action, _) =>
+        println("created copied ")
         action.create(copiedName, Some(origName), Some("copy"), parameters = copiedParams, annotations = copiedAnnots)
       }
 
-      val copiedAction = getJSONFromCLIResponse(wsk.action.get(copiedName).stdout)
+      val copiedAction = getJSONFromResponse(wsk.action.get(copiedName).stdout, wsk.isInstanceOf[Wsk])
 
       // CLI does not guarantee order of annotations and parameters so do a diff to compare the values
       copiedAction.fields("parameters").convertTo[Seq[JsObject]] diff resParams shouldBe List()
@@ -268,16 +269,6 @@ class WskActionTests extends TestHelpers with WskTestHelpers with JsHelpers {
     }
   }
 
-  it should "reject an invoke with the wrong parameters set" in withAssetCleaner(wskprops) { (wp, assetHelper) =>
-    val fullQualifiedName = s"/$guestNamespace/samples/helloWorld"
-    val payload = "bob"
-    val rr = wsk.cli(
-      Seq("action", "invoke", fullQualifiedName, payload) ++ wskprops.overrides,
-      expectedExitCode = TestUtils.ERROR_EXIT)
-    rr.stderr should include("Run 'wsk --help' for usage.")
-    rr.stderr should include(s"error: Invalid argument(s): $payload")
-  }
-
   it should "not be able to use 'ping' in an action" in withAssetCleaner(wskprops) { (wp, assetHelper) =>
     val name = "ping"
     assetHelper.withCleaner(wsk.action, name) { (action, _) =>
diff --git a/tests/src/test/scala/system/basic/WskBasicJavaTests.scala b/tests/src/test/scala/system/basic/WskBasicJavaTests.scala
new file mode 100644
index 00000000..87b38ccb
--- /dev/null
+++ b/tests/src/test/scala/system/basic/WskBasicJavaTests.scala
@@ -0,0 +1,104 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package system.basic
+
+import scala.concurrent.duration.DurationInt
+
+import common.TestHelpers
+import common.TestCLIUtils
+import common.TestUtils.ANY_ERROR_EXIT
+import common.WskTestHelpers
+import common.WskProps
+import common.BaseWsk
+
+import org.junit.runner.RunWith
+import org.scalatest.Matchers
+import org.scalatest.junit.JUnitRunner
+
+import spray.json.JsString
+
+@RunWith(classOf[JUnitRunner])
+abstract class WskBasicJavaTests extends TestHelpers with WskTestHelpers with Matchers {
+
+  implicit val wskprops = WskProps()
+  val wsk: BaseWsk
+  val expectedDuration = 120.seconds
+  val activationPollDuration = 60.seconds
+
+  behavior of "Java Actions"
+
+  /**
+   * Test the Java "hello world" demo sequence
+   */
+  it should "Invoke a java action" in withAssetCleaner(wskprops) { (wp, assetHelper) =>
+    val name = "helloJava"
+    val file = Some(TestCLIUtils.getTestActionFilename("helloJava.jar"))
+
+    assetHelper.withCleaner(wsk.action, name) { (action, _) =>
+      action.create(name, file, main = Some("hello.HelloJava"))
+    }
+
+    val start = System.currentTimeMillis()
+    withActivation(wsk.activation, wsk.action.invoke(name), totalWait = activationPollDuration) {
+      _.response.result.get.toString should include("Hello stranger!")
+    }
+
+    withActivation(
+      wsk.activation,
+      wsk.action.invoke(name, Map("name" -> JsString("Sir"))),
+      totalWait = activationPollDuration) {
+      _.response.result.get.toString should include("Hello Sir!")
+    }
+
+    withClue("Test duration exceeds expectation (ms)") {
+      val duration = System.currentTimeMillis() - start
+      duration should be <= expectedDuration.toMillis
+    }
+  }
+
+  /*
+   * Example from the docs.
+   */
+  it should "Invoke a Java action where main is in the default package" in withAssetCleaner(wskprops) {
+    (wp, assetHelper) =>
+      val name = "helloJavaDefaultPkg"
+      val file = Some(TestCLIUtils.getTestActionFilename("helloJavaDefaultPackage.jar"))
+
+      assetHelper.withCleaner(wsk.action, name) { (action, _) =>
+        action.create(name, file, main = Some("Hello"))
+      }
+
+      withActivation(wsk.activation, wsk.action.invoke(name, Map()), totalWait = activationPollDuration) {
+        _.response.result.get.toString should include("Hello stranger!")
+      }
+  }
+
+  it should "Ensure that Java actions cannot be created without a specified main method" in withAssetCleaner(wskprops) {
+    (wp, assetHelper) =>
+      val name = "helloJavaWithNoMainSpecified"
+      val file = Some(TestCLIUtils.getTestActionFilename("helloJava.jar"))
+
+      val createResult = assetHelper.withCleaner(wsk.action, name, confirmDelete = false) { (action, _) =>
+        action.create(name, file, expectedExitCode = ANY_ERROR_EXIT)
+      }
+
+      val output = s"${createResult.stdout}\n${createResult.stderr}"
+
+      output should include("main")
+  }
+}
diff --git a/tests/src/test/scala/system/basic/WskBasicNodeTests.scala b/tests/src/test/scala/system/basic/WskBasicNodeTests.scala
new file mode 100644
index 00000000..4242c729
--- /dev/null
+++ b/tests/src/test/scala/system/basic/WskBasicNodeTests.scala
@@ -0,0 +1,140 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package system.basic
+
+import org.junit.runner.RunWith
+import org.scalatest.junit.JUnitRunner
+
+import common.JsHelpers
+import common.TestHelpers
+import common.TestCLIUtils
+import common.TestUtils.ANY_ERROR_EXIT
+import common.TestUtils.RunResult
+import common.BaseWsk
+import common.WskProps
+import common.WskTestHelpers
+import spray.json._
+import spray.json.DefaultJsonProtocol._
+
+@RunWith(classOf[JUnitRunner])
+abstract class WskBasicNodeTests extends TestHelpers with WskTestHelpers with JsHelpers {
+
+  implicit val wskprops = WskProps()
+  val wsk: BaseWsk
+  val defaultAction = Some(TestCLIUtils.getTestActionFilename("hello.js"))
+  val currentNodeJsDefaultKind = "nodejs:6"
+
+  behavior of "NodeJS runtime"
+
+  it should "Map a kind of nodejs:default to the current default NodeJS runtime" in withAssetCleaner(wskprops) {
+    (wp, assetHelper) =>
+      val name = "usingDefaultNodeAlias"
+
+      assetHelper.withCleaner(wsk.action, name) { (action, _) =>
+        action.create(name, defaultAction, kind = Some("nodejs:default"))
+      }
+
+      val result = wsk.action.get(name)
+      withPrintOnFailure(result) { () =>
+        val action = convertRunResultToJsObject(result)
+        action.getFieldPath("exec", "kind") should be(Some(currentNodeJsDefaultKind.toJson))
+      }
+  }
+
+  it should "Ensure that NodeJS actions can have a non-default entrypoint" in withAssetCleaner(wskprops) {
+    (wp, assetHelper) =>
+      val name = "niamNpmAction"
+      val file = Some(TestCLIUtils.getTestActionFilename("niam.js"))
+
+      assetHelper.withCleaner(wsk.action, name) { (action, _) =>
+        action.create(name, file, main = Some("niam"))
+      }
+
+      withActivation(wsk.activation, wsk.action.invoke(name)) { activation =>
+        val response = activation.response
+        response.result.get.fields.get("error") shouldBe empty
+        response.result.get.fields.get("greetings") should be(Some(JsString("Hello from a non-standard entrypoint.")))
+      }
+  }
+
+  it should "Ensure that zipped actions are encoded and uploaded as NodeJS actions" in withAssetCleaner(wskprops) {
+    (wp, assetHelper) =>
+      val name = "zippedNpmAction"
+      val file = Some(TestCLIUtils.getTestActionFilename("zippedaction.zip"))
+
+      assetHelper.withCleaner(wsk.action, name) { (action, _) =>
+        action.create(name, file, kind = Some("nodejs:default"))
+      }
+
+      withActivation(wsk.activation, wsk.action.invoke(name)) { activation =>
+        val response = activation.response
+        response.result.get.fields.get("error") shouldBe empty
+        response.result.get.fields.get("author") shouldBe defined
+      }
+  }
+
+  it should "Ensure that zipped actions cannot be created without a kind specified" in withAssetCleaner(wskprops) {
+    (wp, assetHelper) =>
+      val name = "zippedNpmActionWithNoKindSpecified"
+      val file = Some(TestCLIUtils.getTestActionFilename("zippedaction.zip"))
+
+      val createResult = assetHelper.withCleaner(wsk.action, name, confirmDelete = false) { (action, _) =>
+        action.create(name, file, expectedExitCode = ANY_ERROR_EXIT)
+      }
+
+      val output = s"${createResult.stdout}\n${createResult.stderr}"
+
+      output should include("kind")
+  }
+
+  it should "Ensure that JS actions created with no explicit kind use the current default NodeJS runtime" in withAssetCleaner(
+    wskprops) { (wp, assetHelper) =>
+    val name = "jsWithNoKindSpecified"
+
+    assetHelper.withCleaner(wsk.action, name) { (action, _) =>
+      action.create(name, defaultAction)
+    }
+
+    val result = wsk.action.get(name)
+    withPrintOnFailure(result) { () =>
+      val action = convertRunResultToJsObject(result)
+      action.getFieldPath("exec", "kind") should be(Some(currentNodeJsDefaultKind.toJson))
+    }
+  }
+
+  it should "Ensure that returning an empty rejected Promise results in an errored activation" in withAssetCleaner(
+    wskprops) { (wp, assetHelper) =>
+    val name = "jsEmptyRejectPromise"
+
+    assetHelper.withCleaner(wsk.action, name) { (action, _) =>
+      action.create(name, Some(TestCLIUtils.getTestActionFilename("issue-1562.js")))
+    }
+
+    withActivation(wsk.activation, wsk.action.invoke(name)) { activation =>
+      val response = activation.response
+      response.success should be(false)
+      response.result.get.fields.get("error") shouldBe defined
+    }
+  }
+
+  def convertRunResultToJsObject(result: RunResult): JsObject = {
+    val stdout = result.stdout
+    val firstNewline = stdout.indexOf("\n")
+    stdout.substring(firstNewline + 1).parseJson.asJsObject
+  }
+}
diff --git a/tests/src/test/scala/system/basic/WskBasicPythonTests.scala b/tests/src/test/scala/system/basic/WskBasicPythonTests.scala
new file mode 100644
index 00000000..6cb5861e
--- /dev/null
+++ b/tests/src/test/scala/system/basic/WskBasicPythonTests.scala
@@ -0,0 +1,142 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package system.basic
+
+import org.junit.runner.RunWith
+import org.scalatest.junit.JUnitRunner
+import org.scalatest.Matchers
+import scala.concurrent.duration.DurationInt
+import spray.json._
+import spray.json.DefaultJsonProtocol.StringJsonFormat
+import common.JsHelpers
+import common.TestHelpers
+import common.TestCLIUtils
+import common.BaseWsk
+import common.WskProps
+import common.WskTestHelpers
+import common.WhiskProperties
+
+@RunWith(classOf[JUnitRunner])
+abstract class WskBasicPythonTests extends TestHelpers with WskTestHelpers with Matchers with JsHelpers {
+
+  implicit val wskprops = WskProps()
+  val wsk: BaseWsk
+
+  behavior of "Native Python Action"
+
+  it should "invoke an action and get the result" in withAssetCleaner(wskprops) { (wp, assetHelper) =>
+    val name = "basicInvoke"
+    assetHelper.withCleaner(wsk.action, name) { (action, _) =>
+      action.create(name, Some(TestCLIUtils.getTestActionFilename("hello.py")))
+    }
+
+    withActivation(wsk.activation, wsk.action.invoke(name, Map("name" -> "Prince".toJson))) {
+      _.response.result.get.toString should include("Prince")
+    }
+  }
+
+  it should "invoke an action with a non-default entry point" in withAssetCleaner(wskprops) { (wp, assetHelper) =>
+    val name = "nonDefaultEntryPoint"
+    assetHelper.withCleaner(wsk.action, name) { (action, _) =>
+      action.create(name, Some(TestCLIUtils.getTestActionFilename("niam.py")), main = Some("niam"))
+    }
+
+    withActivation(wsk.activation, wsk.action.invoke(name, Map())) {
+      _.response.result.get.fields.get("greetings") should be(Some(JsString("Hello from a non-standard entrypoint.")))
+    }
+  }
+
+  it should "invoke an action from a zip file with a non-default entry point" in withAssetCleaner(wskprops) {
+    (wp, assetHelper) =>
+      val name = "pythonZipWithNonDefaultEntryPoint"
+      assetHelper.withCleaner(wsk.action, name) { (action, _) =>
+        action.create(
+          name,
+          Some(TestCLIUtils.getTestActionFilename("python.zip")),
+          main = Some("niam"),
+          kind = Some("python:default"))
+      }
+
+      withActivation(wsk.activation, wsk.action.invoke(name, Map("name" -> "Prince".toJson))) {
+        _.response.result.get shouldBe JsObject("greeting" -> JsString("Hello Prince!"))
+      }
+  }
+
+  Seq("python:2", "python:3").foreach { kind =>
+    it should s"invoke a $kind action" in withAssetCleaner(wskprops) { (wp, assetHelper) =>
+      val name = s"${kind.replace(":", "")}-action"
+      assetHelper.withCleaner(wsk.action, name) { (action, _) =>
+        action.create(name, Some(TestCLIUtils.getTestActionFilename("pythonVersion.py")), kind = Some(kind))
+      }
+
+      withActivation(wsk.activation, wsk.action.invoke(name)) {
+        _.response.result.get shouldBe JsObject("version" -> JsNumber(kind.takeRight(1).toInt))
+      }
+    }
+  }
+
+  it should "invoke an action and confirm expected environment is defined" in withAssetCleaner(wskprops) {
+    (wp, assetHelper) =>
+      val name = "stdenv"
+      assetHelper.withCleaner(wsk.action, name) { (action, _) =>
+        action.create(name, Some(TestCLIUtils.getTestActionFilename("stdenv.py")))
+      }
+
+      withActivation(wsk.activation, wsk.action.invoke(name)) { activation =>
+        val result = activation.response.result.get
+        result.fields.get("error") shouldBe empty
+        result.fields.get("auth") shouldBe Some(
+          JsString(WhiskProperties.readAuthKey(WhiskProperties.getAuthFileForTesting)))
+        result.fields.get("edge").toString.trim should not be empty
+      }
+  }
+
+  it should "invoke an invalid action and get error back" in withAssetCleaner(wskprops) { (wp, assetHelper) =>
+    val name = "bad code"
+    assetHelper.withCleaner(wsk.action, name) { (action, _) =>
+      action.create(name, Some(TestCLIUtils.getTestActionFilename("malformed.py")))
+    }
+
+    withActivation(wsk.activation, wsk.action.invoke(name)) { activation =>
+      activation.response.result.get.fields.get("error") shouldBe Some(
+        JsString("The action failed to generate or locate a binary. See logs for details."))
+      activation.logs.get.mkString("\n") should { not include ("pythonaction.py") and not include ("flask") }
+    }
+  }
+
+  behavior of "Python virtualenv"
+
+  Seq(("python2_virtualenv.zip", "python:2"), ("python3_virtualenv.zip", "python:3")).foreach {
+    case (filename, kind) =>
+      it should s"invoke a zipped $kind action with virtualenv package" in withAssetCleaner(wskprops) {
+        (wp, assetHelper) =>
+          val name = filename
+          val zippedPythonAction = Some(TestCLIUtils.getTestActionFilename(filename))
+
+          assetHelper.withCleaner(wsk.action, name) { (action, _) =>
+            action.create(name, zippedPythonAction, kind = Some(kind))
+          }
+
+          withActivation(wsk.activation, wsk.action.invoke(name), totalWait = 120.seconds) { activation =>
+            val response = activation.response
+            response.result.get.fields.get("error") shouldBe empty
+            response.result.get.fields.get("Networkinfo: ") shouldBe defined
+          }
+      }
+  }
+}
diff --git a/tests/src/test/scala/system/basic/WskBasicSwift3Tests.scala b/tests/src/test/scala/system/basic/WskBasicSwift3Tests.scala
new file mode 100644
index 00000000..77076b43
--- /dev/null
+++ b/tests/src/test/scala/system/basic/WskBasicSwift3Tests.scala
@@ -0,0 +1,65 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package system.basic
+
+import org.junit.runner.RunWith
+import org.scalatest.junit.JUnitRunner
+
+import common.JsHelpers
+import common.TestHelpers
+import common.TestCLIUtils
+import common.BaseWsk
+import common.WskProps
+import common.WskTestHelpers
+import spray.json.pimpString
+import spray.json.JsString
+import common.TestUtils.RunResult
+import spray.json.JsObject
+
+@RunWith(classOf[JUnitRunner])
+abstract class WskBasicSwift3Tests extends TestHelpers with WskTestHelpers with JsHelpers {
+
+  implicit val wskprops: common.WskProps = WskProps()
+  val wsk: BaseWsk
+  val defaultAction: Some[String] = Some(TestCLIUtils.getTestActionFilename("hello.swift"))
+  lazy val currentSwiftDefaultKind = "swift:3"
+
+  behavior of "Swift runtime"
+
+  it should "Ensure that Swift actions can have a non-default entrypoint" in withAssetCleaner(wskprops) {
+    (wp, assetHelper) =>
+      val name = "niamSwiftAction"
+      val file = Some(TestCLIUtils.getTestActionFilename("niam.swift"))
+
+      assetHelper.withCleaner(wsk.action, name) { (action, _) =>
+        action.create(name, file, main = Some("niam"))
+      }
+
+      withActivation(wsk.activation, wsk.action.invoke(name)) { activation =>
+        val response = activation.response
+        response.result.get.fields.get("error") shouldBe empty
+        response.result.get.fields.get("greetings") should be(Some(JsString("Hello from a non-standard entrypoint.")))
+      }
+  }
+
+  def convertRunResultToJsObject(result: RunResult): JsObject = {
+    val stdout = result.stdout
+    val firstNewline = stdout.indexOf("\n")
+    stdout.substring(firstNewline + 1).parseJson.asJsObject
+  }
+}
diff --git a/tests/src/test/scala/system/basic/WskBasicTests.scala b/tests/src/test/scala/system/basic/WskBasicTests.scala
index f206e64f..9a02bdb4 100644
--- a/tests/src/test/scala/system/basic/WskBasicTests.scala
+++ b/tests/src/test/scala/system/basic/WskBasicTests.scala
@@ -355,8 +355,9 @@ class WskBasicTests extends TestHelpers with WskTestHelpers {
     }
 
     Seq(strErrInput, numErrInput, boolErrInput) foreach { input =>
-      getJSONFromCLIResponse(
-        wsk.action.invoke(name, parameters = input, blocking = true, expectedExitCode = 246).stderr)
+      getJSONFromResponse(
+        wsk.action.invoke(name, parameters = input, blocking = true, expectedExitCode = 246).stderr,
+        wsk.isInstanceOf[Wsk])
         .fields("response")
         .asJsObject
         .fields("result")
diff --git a/tests/src/test/scala/system/basic/WskCliActionTests.scala b/tests/src/test/scala/system/basic/WskCliActionTests.scala
new file mode 100644
index 00000000..b7649f6d
--- /dev/null
+++ b/tests/src/test/scala/system/basic/WskCliActionTests.scala
@@ -0,0 +1,28 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package system.basic
+
+import org.junit.runner.RunWith
+import org.scalatest.junit.JUnitRunner
+
+import common.Wsk
+
+@RunWith(classOf[JUnitRunner])
+class WskCliActionTests extends WskActionTests {
+  override val wsk = new Wsk
+}
diff --git a/tests/src/test/scala/system/basic/WskCliBasicJavaTests.scala b/tests/src/test/scala/system/basic/WskCliBasicJavaTests.scala
new file mode 100644
index 00000000..3d26690b
--- /dev/null
+++ b/tests/src/test/scala/system/basic/WskCliBasicJavaTests.scala
@@ -0,0 +1,28 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package system.basic
+
+import common.Wsk
+
+import org.junit.runner.RunWith
+import org.scalatest.junit.JUnitRunner
+
+@RunWith(classOf[JUnitRunner])
+class WskCliBasicJavaTests extends WskBasicJavaTests {
+  override val wsk: Wsk = new Wsk
+}
diff --git a/tests/src/test/scala/system/basic/WskCliBasicNodeTests.scala b/tests/src/test/scala/system/basic/WskCliBasicNodeTests.scala
new file mode 100644
index 00000000..5c923b93
--- /dev/null
+++ b/tests/src/test/scala/system/basic/WskCliBasicNodeTests.scala
@@ -0,0 +1,28 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package system.basic
+
+import common.Wsk
+
+import org.junit.runner.RunWith
+import org.scalatest.junit.JUnitRunner
+
+@RunWith(classOf[JUnitRunner])
+class WskCliBasicNodeTests extends WskBasicNodeTests {
+  override val wsk: Wsk = new Wsk
+}
diff --git a/tests/src/test/scala/system/basic/WskCliBasicPythonTests.scala b/tests/src/test/scala/system/basic/WskCliBasicPythonTests.scala
new file mode 100644
index 00000000..a0957c24
--- /dev/null
+++ b/tests/src/test/scala/system/basic/WskCliBasicPythonTests.scala
@@ -0,0 +1,28 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package system.basic
+
+import common.Wsk
+
+import org.junit.runner.RunWith
+import org.scalatest.junit.JUnitRunner
+
+@RunWith(classOf[JUnitRunner])
+class WskCliBasicPythonTests extends WskBasicPythonTests {
+  override val wsk: common.Wsk = new Wsk
+}
diff --git a/tests/src/test/scala/system/basic/WskCliBasicSwift311Tests.scala b/tests/src/test/scala/system/basic/WskCliBasicSwift311Tests.scala
new file mode 100644
index 00000000..017dbd16
--- /dev/null
+++ b/tests/src/test/scala/system/basic/WskCliBasicSwift311Tests.scala
@@ -0,0 +1,29 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package system.basic
+
+import common.Wsk
+
+import org.junit.runner.RunWith
+import org.scalatest.junit.JUnitRunner
+
+@RunWith(classOf[JUnitRunner])
+class WskCliBasicSwift311Tests extends WskBasicSwift3Tests {
+  override val wsk = new Wsk
+  override lazy val currentSwiftDefaultKind: String = "swift:3.1.1"
+}
diff --git a/tests/src/test/scala/system/basic/WskCliBasicSwift3Tests.scala b/tests/src/test/scala/system/basic/WskCliBasicSwift3Tests.scala
new file mode 100644
index 00000000..bbe3cd72
--- /dev/null
+++ b/tests/src/test/scala/system/basic/WskCliBasicSwift3Tests.scala
@@ -0,0 +1,28 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package system.basic
+
+import common.Wsk
+
+import org.junit.runner.RunWith
+import org.scalatest.junit.JUnitRunner
+
+@RunWith(classOf[JUnitRunner])
+class WskCliBasicSwift3Tests extends WskBasicSwift3Tests {
+  override val wsk: common.Wsk = new Wsk
+}
diff --git a/tests/src/test/scala/system/basic/WskCliConsoleTests.scala b/tests/src/test/scala/system/basic/WskCliConsoleTests.scala
new file mode 100644
index 00000000..e8652d31
--- /dev/null
+++ b/tests/src/test/scala/system/basic/WskCliConsoleTests.scala
@@ -0,0 +1,30 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package system.basic;
+
+import org.junit.runner.RunWith
+import org.scalatest.junit.JUnitRunner
+import common.Wsk
+
+/**
+ * Tests of the text console
+ */
+@RunWith(classOf[JUnitRunner])
+class WskCliConsoleTests extends WskConsoleTests {
+  override val wsk: Wsk = new Wsk
+}
diff --git a/tests/src/test/scala/system/basic/WskCliPackageTests.scala b/tests/src/test/scala/system/basic/WskCliPackageTests.scala
new file mode 100644
index 00000000..e3a3f847
--- /dev/null
+++ b/tests/src/test/scala/system/basic/WskCliPackageTests.scala
@@ -0,0 +1,28 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package system.basic
+
+import org.junit.runner.RunWith
+import org.scalatest.junit.JUnitRunner
+
+import common.Wsk
+
+@RunWith(classOf[JUnitRunner])
+class WskCliPackageTests extends WskPackageTests {
+  override val wsk: Wsk = new Wsk
+}
diff --git a/tests/src/test/scala/system/basic/WskCliRuleTests.scala b/tests/src/test/scala/system/basic/WskCliRuleTests.scala
new file mode 100644
index 00000000..6c630772
--- /dev/null
+++ b/tests/src/test/scala/system/basic/WskCliRuleTests.scala
@@ -0,0 +1,28 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package system.basic
+
+import org.junit.runner.RunWith
+import org.scalatest.junit.JUnitRunner
+
+import common.Wsk
+
+@RunWith(classOf[JUnitRunner])
+class WskCliRuleTests extends WskRuleTests {
+  override val wsk: Wsk = new Wsk
+}
diff --git a/tests/src/test/scala/system/basic/WskCliSequenceTests.scala b/tests/src/test/scala/system/basic/WskCliSequenceTests.scala
new file mode 100644
index 00000000..e1d0db75
--- /dev/null
+++ b/tests/src/test/scala/system/basic/WskCliSequenceTests.scala
@@ -0,0 +1,32 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package system.basic
+
+import org.junit.runner.RunWith
+import org.scalatest.junit.JUnitRunner
+
+import common.Wsk
+
+/**
+ * Tests sequence execution
+ */
+
+@RunWith(classOf[JUnitRunner])
+class WskCliSequenceTests extends WskSequenceTests {
+  override val wsk: common.Wsk = new Wsk
+}
diff --git a/tests/src/test/scala/system/basic/WskConsoleTests.scala b/tests/src/test/scala/system/basic/WskConsoleTests.scala
index 83526d71..80aae4db 100644
--- a/tests/src/test/scala/system/basic/WskConsoleTests.scala
+++ b/tests/src/test/scala/system/basic/WskConsoleTests.scala
@@ -29,7 +29,7 @@ import org.scalatest.junit.JUnitRunner
 
 import common.TestHelpers
 import common.TestCLIUtils
-import common.Wsk
+import common.BaseWsk
 import common.WskProps
 import common.WskTestHelpers
 import spray.json.DefaultJsonProtocol.IntJsonFormat
@@ -40,10 +40,10 @@ import spray.json.pimpAny
  * Tests of the text console
  */
 @RunWith(classOf[JUnitRunner])
-class WskConsoleTests extends TestHelpers with WskTestHelpers {
+abstract class WskConsoleTests extends TestHelpers with WskTestHelpers {
 
   implicit val wskprops = WskProps()
-  val wsk = new Wsk
+  val wsk: BaseWsk
   val guestNamespace = wskprops.namespace
 
   /**
@@ -70,7 +70,16 @@ class WskConsoleTests extends TestHelpers with WskTestHelpers {
     val start = Instant.now.minusSeconds(5)
     val payload = new String("from the console!".getBytes, "UTF-8")
     val run = wsk.action.invoke(fullActionName, Map("payload" -> payload.toJson))
-    withActivation(wsk.activation, run, totalWait = 30 seconds) { activation =>
+    withActivation(wsk.activation, run, totalWait = 30.seconds) { activation =>
+      // Time recorded by invoker, some contingency to make query more robust
+      val queryTime = activation.start.minusMillis(500)
+      // since: poll for activations since specified point in time (absolute)
+      val activations = wsk.activation.pollFor(N = 1, Some(actionName), since = Some(queryTime), retries = 80).length
+      withClue(
+        s"expected activations of action '${actionName}' since ${queryTime.toString} / initial activation ${activation.activationId}:") {
+        activations should be(1)
+      }
+
       val duration = Duration(Instant.now.minusMillis(start.toEpochMilli).toEpochMilli, MILLISECONDS)
       val pollTime = 10 seconds
       // since: poll for activations since specified number of seconds ago (relative)
diff --git a/tests/src/test/scala/system/basic/WskPackageTests.scala b/tests/src/test/scala/system/basic/WskPackageTests.scala
index 40c93e6a..80686c5c 100644
--- a/tests/src/test/scala/system/basic/WskPackageTests.scala
+++ b/tests/src/test/scala/system/basic/WskPackageTests.scala
@@ -24,7 +24,7 @@ import scala.concurrent.duration.DurationInt
 import org.junit.runner.RunWith
 import org.scalatest.junit.JUnitRunner
 import common.TestCLIUtils
-import common.Wsk
+import common.BaseWsk
 import common.WskProps
 import spray.json._
 import spray.json.DefaultJsonProtocol.StringJsonFormat
@@ -34,10 +34,10 @@ import common.TestHelpers
 import common.WskProps
 
 @RunWith(classOf[JUnitRunner])
-class WskPackageTests extends TestHelpers with WskTestHelpers {
+abstract class WskPackageTests extends TestHelpers with WskTestHelpers {
 
   implicit val wskprops = WskProps()
-  val wsk = new Wsk
+  val wsk: BaseWsk
   val LOG_DELAY = 80 seconds
 
   behavior of "Wsk Package"
@@ -128,9 +128,8 @@ class WskPackageTests extends TestHelpers with WskTestHelpers {
     val flatDescription = itemDescription.replace("\n", "").replace("\r", "")
     merged.foreach {
       case (key: String, value: JsValue) =>
-        val toFind = s""""key": "${key}",.*"value": ${value.toString}"""
+        val toFind = s""""key":.*"${key}",.*"value":.*${value.toString}"""
         flatDescription should include regex toFind
     }
   }
-
 }
diff --git a/tests/src/test/scala/system/basic/WskRuleTests.scala b/tests/src/test/scala/system/basic/WskRuleTests.scala
index adc95a7f..a7929bab 100644
--- a/tests/src/test/scala/system/basic/WskRuleTests.scala
+++ b/tests/src/test/scala/system/basic/WskRuleTests.scala
@@ -21,7 +21,8 @@ import org.junit.runner.RunWith
 import org.scalatest.junit.JUnitRunner
 import common.TestHelpers
 import common.TestCLIUtils
-import common.Wsk
+import common.TestUtils.RunResult
+import common.BaseWsk
 import common.WskProps
 import common.WskTestHelpers
 import spray.json._
@@ -29,10 +30,10 @@ import spray.json.DefaultJsonProtocol._
 import java.time.Instant
 
 @RunWith(classOf[JUnitRunner])
-class WskRuleTests extends TestHelpers with WskTestHelpers {
+abstract class WskRuleTests extends TestHelpers with WskTestHelpers {
 
   implicit val wskprops = WskProps()
-  val wsk = new Wsk
+  val wsk: BaseWsk
   val defaultAction = TestCLIUtils.getTestActionFilename("wc.js")
   val secondAction = TestCLIUtils.getTestActionFilename("hello.js")
   val testString = "this is a test"
@@ -413,10 +414,15 @@ class WskRuleTests extends TestHelpers with WskTestHelpers {
         assetHelper)
 
       wsk.rule.disable(ruleName)
-      val listOutput = wsk.rule.list().stdout.lines
-      listOutput.find(_.contains(ruleName2)).get should (include(ruleName2) and include("active"))
-      listOutput.find(_.contains(ruleName)).get should (include(ruleName) and include("inactive"))
-      wsk.rule.list().stdout should not include ("Unknown")
+      val ruleListResult = wsk.rule.list()
+      verifyRuleList(ruleListResult, ruleName2, ruleName)
   }
 
+  def verifyRuleList(ruleListResult: RunResult, ruleNameEnable: String, ruleName: String) = {
+    val ruleList = ruleListResult.stdout
+    val listOutput = ruleList.lines
+    listOutput.find(_.contains(ruleNameEnable)).get should (include(ruleNameEnable) and include("active"))
+    listOutput.find(_.contains(ruleName)).get should (include(ruleName) and include("inactive"))
+    ruleList should not include ("Unknown")
+  }
 }
diff --git a/tests/src/test/scala/system/basic/WskSdkTests.scala b/tests/src/test/scala/system/basic/WskSdkTests.scala
index 021e8cac..b36c823d 100644
--- a/tests/src/test/scala/system/basic/WskSdkTests.scala
+++ b/tests/src/test/scala/system/basic/WskSdkTests.scala
@@ -18,6 +18,8 @@
 package system.basic
 
 import java.io.File
+import java.io.BufferedWriter
+import java.io.FileWriter
 
 import org.apache.commons.io.FileUtils
 import org.junit.runner.RunWith
@@ -120,4 +122,57 @@ class WskSdkTests extends TestHelpers with WskTestHelpers {
     val stdout = wsk.cli(Seq("sdk", "install", "bashauto", "--stdout")).stdout
     stdout should include(msg)
   }
+
+  def verifyMissingSecurityFile(config: String, fileName: String, expectedErrorMessage: String) = {
+    val tmpwskprops = File.createTempFile("wskprops", ".tmp")
+    val securityFile = File.createTempFile(fileName, ".pem")
+    try {
+      val writer = new BufferedWriter(new FileWriter(tmpwskprops))
+      writer.write(s"$config=${securityFile.getAbsolutePath()}\n")
+      writer.close()
+      val env = Map("WSK_CONFIG_FILE" -> tmpwskprops.getAbsolutePath())
+      val stderr = wsk
+        .cli(
+          Seq("sdk", "install", "docker", "--apihost", wskprops.apihost, "--apiversion", wskprops.apiversion),
+          env = env,
+          expectedExitCode = ERROR_EXIT)
+        .stderr
+      stderr should include regex (expectedErrorMessage)
+    } finally {
+      tmpwskprops.delete()
+      securityFile.delete()
+    }
+  }
+
+  it should "return configure the missing Key file" in {
+    verifyMissingSecurityFile("CERT", "cert", "The Key file is not configured. Please configure the missing Key file.")
+  }
+
+  it should "return configure the missing Cert file" in {
+    verifyMissingSecurityFile("KEY", "key", "The Cert file is not configured. Please configure the missing Cert file.")
+  }
+
+  it should "return unable to load the X509 key pair with both Cert and Key files missing" in {
+    val tmpwskprops = File.createTempFile("wskprops", ".tmp")
+    val certFile = File.createTempFile("cert", ".pem")
+    val keyFile = File.createTempFile("key", ".pem")
+    try {
+      val writer = new BufferedWriter(new FileWriter(tmpwskprops))
+      writer.write(s"CERT=${certFile.getAbsolutePath()}\n")
+      writer.write(s"KEY=${keyFile.getAbsolutePath()}\n")
+      writer.close()
+      val env = Map("WSK_CONFIG_FILE" -> tmpwskprops.getAbsolutePath())
+      val stderr = wsk
+        .cli(
+          Seq("sdk", "install", "docker", "--apihost", wskprops.apihost, "--apiversion", wskprops.apiversion),
+          env = env,
+          expectedExitCode = ERROR_EXIT)
+        .stderr
+      stderr should include regex ("""Unable to load the X509 key pair due to the following reason""")
+    } finally {
+      tmpwskprops.delete()
+      certFile.delete()
+      keyFile.delete()
+    }
+  }
 }
diff --git a/tests/src/test/scala/system/basic/WskSequenceTests.scala b/tests/src/test/scala/system/basic/WskSequenceTests.scala
index 9ec21a74..440c2bad 100644
--- a/tests/src/test/scala/system/basic/WskSequenceTests.scala
+++ b/tests/src/test/scala/system/basic/WskSequenceTests.scala
@@ -33,7 +33,7 @@ import common.TestHelpers
 import common.TestCLIUtils
 import common.TestUtils._
 import common.WhiskProperties
-import common.Wsk
+import common.BaseWsk
 import common.WskProps
 import common.WskTestHelpers
 
@@ -47,12 +47,11 @@ import whisk.http.Messages.sequenceIsTooLong
 /**
  * Tests sequence execution
  */
-
 @RunWith(classOf[JUnitRunner])
-class WskSequenceTests extends TestHelpers with ScalatestRouteTest with WskTestHelpers with StreamLogging {
+abstract class WskSequenceTests extends TestHelpers with ScalatestRouteTest with WskTestHelpers with StreamLogging {
 
   implicit val wskprops = WskProps()
-  val wsk = new Wsk
+  val wsk: BaseWsk
   val allowedActionDuration = 120 seconds
   val shortDuration = 10 seconds
 
diff --git a/tests/src/test/scala/whisk/core/admin/WskAdminTests.scala b/tests/src/test/scala/whisk/core/admin/WskAdminTests.scala
deleted file mode 100644
index c42e2e8c..00000000
--- a/tests/src/test/scala/whisk/core/admin/WskAdminTests.scala
+++ /dev/null
@@ -1,182 +0,0 @@
-/*
- * Licensed to the Apache Software Foundation (ASF) under one or more
- * contributor license agreements.  See the NOTICE file distributed with
- * this work for additional information regarding copyright ownership.
- * The ASF licenses this file to You under the Apache License, Version 2.0
- * (the "License"); you may not use this file except in compliance with
- * the License.  You may obtain a copy of the License at
- *
- *     http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package whisk.core.admin
-
-import scala.concurrent.duration.DurationInt
-
-import org.junit.runner.RunWith
-import org.scalatest.Matchers
-import org.scalatest.junit.JUnitRunner
-
-import common.RunWskAdminCmd
-import common.TestHelpers
-import common.Wsk
-import common.WskAdmin
-import common.WskProps
-import whisk.core.entity.AuthKey
-import whisk.core.entity.Subject
-import common.TestUtils
-
-@RunWith(classOf[JUnitRunner])
-class WskAdminTests extends TestHelpers with Matchers {
-
-  behavior of "Wsk Admin CLI"
-
-  it should "confirm wskadmin exists" in {
-    WskAdmin.exists
-  }
-
-  it should "CRD a subject" in {
-    val wskadmin = new RunWskAdminCmd {}
-    val auth = AuthKey()
-    val subject = Subject().asString
-    try {
-      println(s"CRD subject: $subject")
-      val create = wskadmin.cli(Seq("user", "create", subject))
-      val get = wskadmin.cli(Seq("user", "get", subject))
-      create.stdout should be(get.stdout)
-
-      val authkey = get.stdout.trim
-      authkey should include(":")
-      authkey.split(":")(0).length should be(36)
-      authkey.split(":")(1).length should be >= 64
-
-      wskadmin.cli(Seq("user", "whois", authkey)).stdout.trim should be(
-        Seq(s"subject: $subject", s"namespace: $subject").mkString("\n"))
-
-      whisk.utils.retry({
-        // reverse lookup by namespace
-        wskadmin.cli(Seq("user", "list", "-k", subject)).stdout.trim should be(authkey)
-      }, 10, Some(1.second))
-
-      wskadmin.cli(Seq("user", "delete", subject)).stdout should include("Subject deleted")
-
-      // recreate with explicit
-      val newspace = s"${subject}.myspace"
-      wskadmin.cli(Seq("user", "create", subject, "-ns", newspace, "-u", auth.compact))
-
-      whisk.utils.retry({
-        // reverse lookup by namespace
-        wskadmin.cli(Seq("user", "list", "-k", newspace)).stdout.trim should be(auth.compact)
-      }, 10, Some(1.second))
-
-      wskadmin.cli(Seq("user", "get", subject, "-ns", newspace)).stdout.trim should be(auth.compact)
-
-      // delete namespace
-      wskadmin.cli(Seq("user", "delete", subject, "-ns", newspace)).stdout should include("Namespace deleted")
-    } finally {
-      wskadmin.cli(Seq("user", "delete", subject)).stdout should include("Subject deleted")
-    }
-  }
-
-  it should "verify guest account installed correctly" in {
-    val wskadmin = new RunWskAdminCmd {}
-    implicit val wskprops = WskProps()
-    val wsk = new Wsk
-    val ns = wsk.namespace.whois()
-    wskadmin.cli(Seq("user", "get", ns)).stdout.trim should be(wskprops.authKey)
-  }
-
-  it should "block and unblock a user respectively" in {
-    val wskadmin = new RunWskAdminCmd {}
-    val auth = AuthKey()
-    val subject1 = Subject().asString
-    val subject2 = Subject().asString
-    val commonNamespace = "testspace"
-    try {
-      wskadmin.cli(Seq("user", "create", subject1, "-ns", commonNamespace, "-u", auth.compact))
-      wskadmin.cli(Seq("user", "create", subject2, "-ns", commonNamespace))
-
-      whisk.utils.retry({
-        // reverse lookup by namespace
-        val out = wskadmin.cli(Seq("user", "list", "-p", "2", "-k", commonNamespace)).stdout.trim
-        out should include(auth.compact)
-        out.lines should have size 2
-      }, 10, Some(1.second))
-
-      // block the user
-      wskadmin.cli(Seq("user", "block", subject1))
-
-      // wait until the user can no longer be found
-      whisk.utils.retry({
-        wskadmin.cli(Seq("user", "list", "-p", "2", "-k", commonNamespace)).stdout.trim.lines should have size 1
-      }, 10, Some(1.second))
-
-      // unblock the user
-      wskadmin.cli(Seq("user", "unblock", subject1))
-
-      // wait until the user can be found again
-      whisk.utils.retry({
-        val out = wskadmin.cli(Seq("user", "list", "-p", "2", "-k", commonNamespace)).stdout.trim
-        out should include(auth.compact)
-        out.lines should have size 2
-      }, 10, Some(1.second))
-    } finally {
-      wskadmin.cli(Seq("user", "delete", subject1)).stdout should include("Subject deleted")
-      wskadmin.cli(Seq("user", "delete", subject2)).stdout should include("Subject deleted")
-    }
-  }
-
-  it should "not allow edits on a blocked subject" in {
-    val wskadmin = new RunWskAdminCmd {}
-    val subject = Subject().asString
-    try {
-      // initially create the subject
-      wskadmin.cli(Seq("user", "create", subject))
-      // editing works
-      wskadmin.cli(Seq("user", "create", subject, "-ns", "testspace1"))
-      // block it
-      wskadmin.cli(Seq("user", "block", subject))
-      // Try to add a namespace, doesn't work
-      wskadmin.cli(Seq("user", "create", subject, "-ns", "testspace2"), expectedExitCode = TestUtils.ERROR_EXIT)
-      // Unblock the user
-      wskadmin.cli(Seq("user", "unblock", subject))
-      // Adding a namespace works
-      wskadmin.cli(Seq("user", "create", subject, "-ns", "testspace2"))
-    } finally {
-      wskadmin.cli(Seq("user", "delete", subject)).stdout should include("Subject deleted")
-    }
-  }
-
-  it should "adjust throttles for namespace" in {
-    val wskadmin = new RunWskAdminCmd {}
-    val subject = Subject().asString
-    try {
-      // set some limits
-      wskadmin.cli(
-        Seq(
-          "limits",
-          "set",
-          subject,
-          "--invocationsPerMinute",
-          "1",
-          "--firesPerMinute",
-          "2",
-          "--concurrentInvocations",
-          "3"))
-      // check correctly set
-      val lines = wskadmin.cli(Seq("limits", "get", subject)).stdout.lines.toSeq
-      lines should have size 3
-      lines(0) shouldBe "invocationsPerMinute = 1"
-      lines(1) shouldBe "firesPerMinute = 2"
-      lines(2) shouldBe "concurrentInvocations = 3"
-    } finally {
-      wskadmin.cli(Seq("limits", "delete", subject)).stdout should include("Limits deleted")
-    }
-  }
-}
diff --git a/tests/src/test/scala/whisk/core/apigw/actions/test/ApiGwCliRoutemgmtActionTests.scala b/tests/src/test/scala/whisk/core/apigw/actions/test/ApiGwCliRoutemgmtActionTests.scala
new file mode 100644
index 00000000..4f0f1316
--- /dev/null
+++ b/tests/src/test/scala/whisk/core/apigw/actions/test/ApiGwCliRoutemgmtActionTests.scala
@@ -0,0 +1,28 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package whisk.core.apigw.actions.test
+
+import org.junit.runner.RunWith
+import org.scalatest.junit.JUnitRunner
+
+import common.Wsk
+
+@RunWith(classOf[JUnitRunner])
+class ApiGwCliRoutemgmtActionTests extends ApiGwRoutemgmtActionTests {
+  override lazy val wsk = new Wsk
+}
diff --git a/tests/src/test/scala/whisk/core/apigw/actions/test/ApiGwRoutemgmtActionTests.scala b/tests/src/test/scala/whisk/core/apigw/actions/test/ApiGwRoutemgmtActionTests.scala
index fa56de62..57449e69 100644
--- a/tests/src/test/scala/whisk/core/apigw/actions/test/ApiGwRoutemgmtActionTests.scala
+++ b/tests/src/test/scala/whisk/core/apigw/actions/test/ApiGwRoutemgmtActionTests.scala
@@ -24,38 +24,23 @@ import org.scalatest.junit.JUnitRunner
 import common.JsHelpers
 import common.StreamLogging
 import common.TestHelpers
-import common.TestUtils.ANY_ERROR_EXIT
 import common.TestUtils.DONTCARE_EXIT
 import common.TestUtils.RunResult
 import common.TestUtils.SUCCESS_EXIT
-import common.Wsk
+import common.BaseWsk
 import common.WskActorSystem
 import common.WskAdmin
 import common.WskProps
+import common.rest.ApiAction
 import common.WskTestHelpers
 import spray.json._
 import spray.json.DefaultJsonProtocol._
 
-case class ApiAction(name: String,
-                     namespace: String,
-                     backendMethod: String = "POST",
-                     backendUrl: String,
-                     authkey: String) {
-  def toJson(): JsObject = {
-    return JsObject(
-      "name" -> name.toJson,
-      "namespace" -> namespace.toJson,
-      "backendMethod" -> backendMethod.toJson,
-      "backendUrl" -> backendUrl.toJson,
-      "authkey" -> authkey.toJson)
-  }
-}
-
 /**
  * Tests for basic CLI usage. Some of these tests require a deployed backend.
  */
 @RunWith(classOf[JUnitRunner])
-class ApiGwRoutemgmtActionTests
+abstract class ApiGwRoutemgmtActionTests
     extends TestHelpers
     with BeforeAndAfterAll
     with WskActorSystem
@@ -65,7 +50,7 @@ class ApiGwRoutemgmtActionTests
 
   val systemId = "whisk.system"
   implicit val wskprops = WskProps(authKey = WskAdmin.listKeys(systemId)(0)._1, namespace = systemId)
-  val wsk = new Wsk
+  val wsk: BaseWsk
 
   def getApis(bpOrName: Option[String],
               relpath: Option[String] = None,
@@ -299,7 +284,7 @@ class ApiGwRoutemgmtActionTests
     val actionUrl = "https://some.whisk.host/api/v1/web/" + actionNamespace + "/default/" + actionName + ".json"
     val actionAuthKey = testName + "_authkey"
     val testaction =
-      ApiAction(name = actionName, namespace = actionNamespace, backendUrl = actionUrl, authkey = actionAuthKey)
+      new ApiAction(name = actionName, namespace = actionNamespace, backendUrl = actionUrl, authkey = actionAuthKey)
 
     try {
       val createResult = createApi(
@@ -330,7 +315,7 @@ class ApiGwRoutemgmtActionTests
     val actionUrl = "https://some.whisk.host/api/v1/web/" + actionNamespace + "/default/" + actionName + ".json"
     val actionAuthKey = testName + "_authkey"
     val testaction =
-      ApiAction(name = actionName, namespace = actionNamespace, backendUrl = actionUrl, authkey = actionAuthKey)
+      new ApiAction(name = actionName, namespace = actionNamespace, backendUrl = actionUrl, authkey = actionAuthKey)
 
     try {
       val createResult = createApi(
@@ -366,7 +351,7 @@ class ApiGwRoutemgmtActionTests
     val actionUrl = "https://some.whisk.host/api/v1/web/" + actionNamespace + "/default/" + actionName + ".json"
     val actionAuthKey = testName + "_authkey"
     val testaction =
-      ApiAction(name = actionName, namespace = actionNamespace, backendUrl = actionUrl, authkey = actionAuthKey)
+      new ApiAction(name = actionName, namespace = actionNamespace, backendUrl = actionUrl, authkey = actionAuthKey)
 
     try {
       var createResult = createApi(
@@ -393,202 +378,4 @@ class ApiGwRoutemgmtActionTests
         deleteApi(namespace = Some(wskprops.namespace), basepath = Some(testbasepath), expectedExitCode = DONTCARE_EXIT)
     }
   }
-
-  it should "reject apimgmt actions that are invoked with not enough parameters" in {
-    val invalidArgs = Seq(
-      //getApi
-      ("/whisk.system/apimgmt/getApi", ANY_ERROR_EXIT, "Invalid authentication.", Seq()),
-      //deleteApi
-      (
-        "/whisk.system/apimgmt/deleteApi",
-        ANY_ERROR_EXIT,
-        "Invalid authentication.",
-        Seq("-p", "basepath", "/ApiGwRoutemgmtActionTests_bp")),
-      (
-        "/whisk.system/apimgmt/deleteApi",
-        ANY_ERROR_EXIT,
-        "basepath is required",
-        Seq("-p", "__ow_user", "_", "-p", "accesstoken", "TOKEN")),
-      (
-        "/whisk.system/apimgmt/deleteApi",
-        ANY_ERROR_EXIT,
-        "When specifying an operation, the path is required",
-        Seq(
-          "-p",
-          "__ow_user",
-          "_",
-          "-p",
-          "accesstoken",
-          "TOKEN",
-          "-p",
-          "basepath",
-          "/ApiGwRoutemgmtActionTests_bp",
-          "-p",
-          "operation",
-          "get")),
-      //createApi
-      (
-        "/whisk.system/apimgmt/createApi",
-        ANY_ERROR_EXIT,
-        "apidoc is required",
-        Seq("-p", "__ow_user", "_", "-p", "accesstoken", "TOKEN")),
-      (
-        "/whisk.system/apimgmt/createApi",
-        ANY_ERROR_EXIT,
-        "apidoc is missing the namespace field",
-        Seq("-p", "__ow_user", "_", "-p", "accesstoken", "TOKEN", "-p", "apidoc", "{}")),
-      (
-        "/whisk.system/apimgmt/createApi",
-        ANY_ERROR_EXIT,
-        "apidoc is missing the gatewayBasePath field",
-        Seq("-p", "__ow_user", "_", "-p", "accesstoken", "TOKEN", "-p", "apidoc", """{"namespace":"_"}""")),
-      (
-        "/whisk.system/apimgmt/createApi",
-        ANY_ERROR_EXIT,
-        "apidoc is missing the gatewayPath field",
-        Seq(
-          "-p",
-          "__ow_user",
-          "_",
-          "-p",
-          "accesstoken",
-          "TOKEN",
-          "-p",
-          "apidoc",
-          """{"namespace":"_","gatewayBasePath":"/ApiGwRoutemgmtActionTests_bp"}""")),
-      (
-        "/whisk.system/apimgmt/createApi",
-        ANY_ERROR_EXIT,
-        "apidoc is missing the gatewayMethod field",
-        Seq(
-          "-p",
-          "__ow_user",
-          "_",
-          "-p",
-          "accesstoken",
-          "TOKEN",
-          "-p",
-          "apidoc",
-          """{"namespace":"_","gatewayBasePath":"/ApiGwRoutemgmtActionTests_bp","gatewayPath":"ApiGwRoutemgmtActionTests_rp"}""")),
-      (
-        "/whisk.system/apimgmt/createApi",
-        ANY_ERROR_EXIT,
-        "apidoc is missing the action field",
-        Seq(
-          "-p",
-          "__ow_user",
-          "_",
-          "-p",
-          "accesstoken",
-          "TOKEN",
-          "-p",
-          "apidoc",
-          """{"namespace":"_","gatewayBasePath":"/ApiGwRoutemgmtActionTests_bp","gatewayPath":"ApiGwRoutemgmtActionTests_rp","gatewayMethod":"get"}""")),
-      (
-        "/whisk.system/apimgmt/createApi",
-        ANY_ERROR_EXIT,
-        "action is missing the backendMethod field",
-        Seq(
-          "-p",
-          "__ow_user",
-          "_",
-          "-p",
-          "accesstoken",
-          "TOKEN",
-          "-p",
-          "apidoc",
-          """{"namespace":"_","gatewayBasePath":"/ApiGwRoutemgmtActionTests_bp","gatewayPath":"ApiGwRoutemgmtActionTests_rp","gatewayMethod":"get","action":{}}""")),
-      (
-        "/whisk.system/apimgmt/createApi",
-        ANY_ERROR_EXIT,
-        "action is missing the backendUrl field",
-        Seq(
-          "-p",
-          "__ow_user",
-          "_",
-          "-p",
-          "accesstoken",
-          "TOKEN",
-          "-p",
-          "apidoc",
-          """{"namespace":"_","gatewayBasePath":"/ApiGwRoutemgmtActionTests_bp","gatewayPath":"ApiGwRoutemgmtActionTests_rp","gatewayMethod":"get","action":{"backendMethod":"post"}}""")),
-      (
-        "/whisk.system/apimgmt/createApi",
-        ANY_ERROR_EXIT,
-        "action is missing the namespace field",
-        Seq(
-          "-p",
-          "__ow_user",
-          "_",
-          "-p",
-          "accesstoken",
-          "TOKEN",
-          "-p",
-          "apidoc",
-          """{"namespace":"_","gatewayBasePath":"/ApiGwRoutemgmtActionTests_bp","gatewayPath":"ApiGwRoutemgmtActionTests_rp","gatewayMethod":"get","action":{"backendMethod":"post","backendUrl":"URL"}}""")),
-      (
-        "/whisk.system/apimgmt/createApi",
-        ANY_ERROR_EXIT,
-        "action is missing the name field",
-        Seq(
-          "-p",
-          "__ow_user",
-          "_",
-          "-p",
-          "accesstoken",
-          "TOKEN",
-          "-p",
-          "apidoc",
-          """{"namespace":"_","gatewayBasePath":"/ApiGwRoutemgmtActionTests_bp","gatewayPath":"ApiGwRoutemgmtActionTests_rp","gatewayMethod":"get","action":{"backendMethod":"post","backendUrl":"URL","namespace":"_"}}""")),
-      (
-        "/whisk.system/apimgmt/createApi",
-        ANY_ERROR_EXIT,
-        "action is missing the authkey field",
-        Seq(
-          "-p",
-          "__ow_user",
-          "_",
-          "-p",
-          "accesstoken",
-          "TOKEN",
-          "-p",
-          "apidoc",
-          """{"namespace":"_","gatewayBasePath":"/ApiGwRoutemgmtActionTests_bp","gatewayPath":"ApiGwRoutemgmtActionTests_rp","gatewayMethod":"get","action":{"backendMethod":"post","backendUrl":"URL","namespace":"_","name":"N"}}""")),
-      (
-        "/whisk.system/apimgmt/createApi",
-        ANY_ERROR_EXIT,
-        "swagger and gatewayBasePath are mutually exclusive and cannot be specified together",
-        Seq(
-          "-p",
-          "__ow_user",
-          "_",
-          "-p",
-          "accesstoken",
-          "TOKEN",
-          "-p",
-          "apidoc",
-          """{"namespace":"_","gatewayBasePath":"/ApiGwRoutemgmtActionTests_bp","gatewayPath":"ApiGwRoutemgmtActionTests_rp","gatewayMethod":"get","action":{"backendMethod":"post","backendUrl":"URL","namespace":"_","name":"N","authkey":"XXXX"},"swagger":{}}""")),
-      (
-        "/whisk.system/apimgmt/createApi",
-        ANY_ERROR_EXIT,
-        "apidoc field cannot be parsed. Ensure it is valid JSON",
-        Seq("-p", "__ow_user", "_", "-p", "accesstoken", "TOKEN", "-p", "apidoc", "{1:[}}}")))
-
-    invalidArgs foreach {
-      case (action: String, exitcode: Int, errmsg: String, params: Seq[String]) =>
-        val cmd: Seq[String] = Seq(
-          "action",
-          "invoke",
-          action,
-          "-i",
-          "-b",
-          "-r",
-          "--apihost",
-          wskprops.apihost,
-          "--auth",
-          wskprops.authKey) ++ params
-        val rr = wsk.cli(cmd, expectedExitCode = exitcode)
-        rr.stderr should include regex (errmsg)
-    }
-  }
 }
diff --git a/tests/src/test/scala/whisk/core/cli/test/ApiGwCliTests.scala b/tests/src/test/scala/whisk/core/cli/test/ApiGwCliTests.scala
new file mode 100644
index 00000000..ba886c0f
--- /dev/null
+++ b/tests/src/test/scala/whisk/core/cli/test/ApiGwCliTests.scala
@@ -0,0 +1,33 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package whisk.core.cli.test
+
+import org.junit.runner.RunWith
+import org.scalatest.junit.JUnitRunner
+
+import common.Wsk
+import common.TestUtils.SUCCESS_EXIT
+
+/**
+ * Tests for basic CLI usage. Some of these tests require a deployed backend.
+ */
+@RunWith(classOf[JUnitRunner])
+class ApiGwCliTests extends ApiGwTests {
+  override lazy val wsk: common.Wsk = new Wsk
+  override lazy val createCode = SUCCESS_EXIT
+}
diff --git a/tests/src/test/scala/whisk/core/cli/test/ApiGwTests.scala b/tests/src/test/scala/whisk/core/cli/test/ApiGwTests.scala
index 0d4b2685..ce384499 100644
--- a/tests/src/test/scala/whisk/core/cli/test/ApiGwTests.scala
+++ b/tests/src/test/scala/whisk/core/cli/test/ApiGwTests.scala
@@ -20,149 +20,147 @@ package whisk.core.cli.test
 import java.io.File
 import java.io.BufferedWriter
 import java.io.FileWriter
-import java.time.Instant
-
-import scala.collection.mutable.ArrayBuffer
-import scala.concurrent.duration._
 
 import org.junit.runner.RunWith
 
-import org.scalatest.BeforeAndAfterAll
-import org.scalatest.BeforeAndAfterEach
 import org.scalatest.junit.JUnitRunner
 
-import common.TestHelpers
 import common.TestUtils._
 import common.TestCLIUtils
-import common.WhiskProperties
-import common.Wsk
 import common.WskProps
-import common.WskTestHelpers
 
 /**
  * Tests for testing the CLI "api" subcommand.  Most of these tests require a deployed backend.
  */
 @RunWith(classOf[JUnitRunner])
-class ApiGwTests extends TestHelpers with WskTestHelpers with BeforeAndAfterEach with BeforeAndAfterAll {
+abstract class ApiGwTests extends BaseApiGwTests {
 
-  implicit val wskprops = WskProps()
-  val wsk = new Wsk
   val clinamespace = wsk.namespace.whois()
+  val createCode: Int
 
-  // This test suite makes enough CLI invocations in 60 seconds to trigger the OpenWhisk
-  // throttling restriction.  To avoid CLI failures due to being throttled, track the
-  // CLI invocation calls and when at the throttle limit, pause the next CLI invocation
-  // with exactly enough time to relax the throttling.
-  val maxActionsPerMin = WhiskProperties.getMaxActionInvokesPerMinute()
-  val invocationTimes = new ArrayBuffer[Instant]()
-
-  // Custom CLI properties file
-  val cliWskPropsFile = File.createTempFile("wskprops", ".tmp")
-
-  /**
-   * Expected to be called before each test.
-   * Assumes that each test will not invoke more than 5 actions and
-   * settle the throttle when there isn't enough capacity to handle the test.
-   */
-  def checkThrottle(maxInvocationsBeforeThrottle: Int = maxActionsPerMin, expectedActivationsPerTest: Int = 5) = {
-    val t = Instant.now
-    val tminus60 = t.minusSeconds(60)
-    val invocationsLast60Seconds = invocationTimes.filter(_.isAfter(tminus60)).sorted
-    val invocationCount = invocationsLast60Seconds.length
-    println(s"Action invokes within last minute: ${invocationCount}")
-
-    if (invocationCount >= maxInvocationsBeforeThrottle) {
-      // Instead of waiting a fixed 60 seconds to settle the throttle,
-      // calculate a wait time that will clear out about half of the
-      // current invocations (assuming even distribution) from the
-      // next 60 second period.
-      val oldestInvocationInLast60Seconds = invocationsLast60Seconds.head
-
-      // Take the oldest invocation time in this 60 second period.  To clear
-      // this invocation from the next 60 second period, the wait time will be
-      // (60sec - oldest invocation's delta time away from the period end).
-      // This will clear all of the invocations from the next period at the
-      // expense of potentially waiting uncessarily long. Instead, this calculation
-      // halves the delta time as a compromise.
-      val throttleTime = 60.seconds.toMillis - ((t.toEpochMilli - oldestInvocationInLast60Seconds.toEpochMilli) / 2)
-      println(s"Waiting ${throttleTime} milliseconds to settle the throttle")
-      Thread.sleep(throttleTime)
-    }
+  def verifyBadCommands(rr: RunResult, badpath: String): Unit = {
+    rr.stderr should include(s"'${badpath}' must begin with '/'")
+  }
+
+  def verifyBadCommandsDelete(rr: RunResult, badpath: String): Unit = {
+    verifyBadCommands(rr, badpath)
+  }
+
+  def verifyBadCommandsList(rr: RunResult, badpath: String): Unit = {
+    verifyBadCommands(rr, badpath)
+  }
+
+  def verifyInvalidCommands(rr: RunResult, badverb: String): Unit = {
+    rr.stderr should include(s"'${badverb}' is not a valid API verb.  Valid values are:")
+  }
+
+  def verifyInvalidCommandsDelete(rr: RunResult, badverb: String): Unit = {
+    verifyInvalidCommands(rr, badverb)
+  }
+
+  def verifyInvalidCommandsList(rr: RunResult, badverb: String): Unit = {
+    verifyInvalidCommands(rr, badverb)
+  }
+
+  def verifyNonJsonSwagger(rr: RunResult, filename: String): Unit = {
+    rr.stderr should include(s"Error parsing swagger file '${filename}':")
+  }
+
+  def verifyMissingField(rr: RunResult): Unit = {
+    rr.stderr should include(s"Swagger file is invalid (missing basePath, info, paths, or swagger fields")
+  }
+
+  def verifyApiCreated(rr: RunResult): Unit = {
+    rr.stdout should include("ok: created API")
+  }
 
-    invocationTimes += Instant.now
+  def verifyApiList(rr: RunResult,
+                    clinamespace: String,
+                    actionName: String,
+                    testurlop: String,
+                    testbasepath: String,
+                    testrelpath: String,
+                    testapiname: String): Unit = {
+    rr.stdout should include("ok: APIs")
+    rr.stdout should include regex (s"Action:\\s+/${clinamespace}/${actionName}\n")
+    rr.stdout should include regex (s"Verb:\\s+${testurlop}\n")
+    rr.stdout should include regex (s"Base path:\\s+${testbasepath}\n")
+    rr.stdout should include regex (s"Path:\\s+${testrelpath}\n")
+    rr.stdout should include regex (s"API Name:\\s+${testapiname}\n")
+    rr.stdout should include regex (s"URL:\\s+")
+    rr.stdout should include(testbasepath + testrelpath)
   }
 
-  override def beforeEach() = {
-    //checkThrottle()
+  def verifyApiBaseRelPath(rr: RunResult, testbasepath: String, testrelpath: String): Unit = {
+    rr.stdout should include(testbasepath + testrelpath)
   }
 
-  /*
-   * Create a CLI properties file for use by the tests
-   */
-  override def beforeAll() = {
-    cliWskPropsFile.deleteOnExit()
-    val wskprops = WskProps(token = "SOME TOKEN")
-    wskprops.writeFile(cliWskPropsFile)
-    println(s"wsk temporary props file created here: ${cliWskPropsFile.getCanonicalPath()}")
+  def verifyApiGet(rr: RunResult): Unit = {
+    rr.stdout should include regex (s""""operationId":\\s+"getPathWithSub_pathsInIt"""")
   }
 
-  /*
-   * Forcibly clear the throttle so that downstream tests are not affected by
-   * this test suite
-   */
-  override def afterAll() = {
-    // Check and settle the throttle so that this test won't cause issues with and follow on tests
-    checkThrottle(30)
+  def verifyApiFullList(rr: RunResult,
+                        clinamespace: String,
+                        actionName: String,
+                        testurlop: String,
+                        testbasepath: String,
+                        testrelpath: String,
+                        testapiname: String): Unit = {
+
+    rr.stdout should include("ok: APIs")
+    if (clinamespace == "") {
+      rr.stdout should include regex (s"/[@\\w._\\-]+/${actionName}\\s+${testurlop}\\s+${testapiname}\\s+")
+    } else {
+      rr.stdout should include regex (s"/${clinamespace}/${actionName}\\s+${testurlop}\\s+${testapiname}\\s+")
+    }
+    rr.stdout should include(testbasepath + testrelpath)
+
+  }
+
+  def verifyApiFullListDouble(rr: RunResult,
+                              clinamespace: String,
+                              actionName: String,
+                              testurlop: String,
+                              testbasepath: String,
+                              testrelpath: String,
+                              testapiname: String,
+                              newEndpoint: String): Unit = {
+    verifyApiFullList(rr, clinamespace, actionName, testurlop, testbasepath, testrelpath, testapiname)
+    rr.stdout should include(testbasepath + newEndpoint)
+  }
+
+  def verifyApiDeleted(rr: RunResult): Unit = {
+    rr.stdout should include("ok: deleted API")
   }
 
-  def apiCreate(basepath: Option[String] = None,
-                relpath: Option[String] = None,
-                operation: Option[String] = None,
-                action: Option[String] = None,
-                apiname: Option[String] = None,
-                swagger: Option[String] = None,
-                responsetype: Option[String] = None,
-                expectedExitCode: Int = SUCCESS_EXIT,
-                cliCfgFile: Option[String] = Some(cliWskPropsFile.getCanonicalPath()))(
-    implicit wskpropsOverride: WskProps): RunResult = {
-
-    checkThrottle()
-    wsk.api.create(basepath, relpath, operation, action, apiname, swagger, responsetype, expectedExitCode, cliCfgFile)(
-      wskpropsOverride)
+  def verifyApiDeletedRelpath(rr: RunResult, testrelpath: String, testbasepath: String, op: String = ""): Unit = {
+    if (op != "")
+      rr.stdout should include("ok: deleted " + testrelpath + " " + op.toUpperCase() + " from " + testbasepath)
+    else
+      rr.stdout should include("ok: deleted " + testrelpath + " from " + testbasepath)
   }
 
-  def apiList(basepathOrApiName: Option[String] = None,
-              relpath: Option[String] = None,
-              operation: Option[String] = None,
-              limit: Option[Int] = None,
-              since: Option[Instant] = None,
-              full: Option[Boolean] = None,
-              nameSort: Option[Boolean] = None,
-              expectedExitCode: Int = SUCCESS_EXIT,
-              cliCfgFile: Option[String] = Some(cliWskPropsFile.getCanonicalPath())): RunResult = {
-
-    checkThrottle()
-    wsk.api.list(basepathOrApiName, relpath, operation, limit, since, full, nameSort, expectedExitCode, cliCfgFile)
+  def verifyApiNameGet(rr: RunResult, testbasepath: String, actionName: String, responseType: String = "json"): Unit = {
+    rr.stdout should include(testbasepath)
+    rr.stdout should include(s"${actionName}")
+    rr.stdout should include regex (""""cors":\s*\{\s*\n\s*"enabled":\s*true""")
+    rr.stdout should include regex (s""""target-url":\\s+.*${actionName}.${responseType}""")
   }
 
-  def apiGet(basepathOrApiName: Option[String] = None,
-             full: Option[Boolean] = None,
-             expectedExitCode: Int = SUCCESS_EXIT,
-             cliCfgFile: Option[String] = Some(cliWskPropsFile.getCanonicalPath()),
-             format: Option[String] = None): RunResult = {
+  def verifyInvalidSwagger(rr: RunResult): Unit = {
+    rr.stderr should include(s"Swagger file is invalid")
+  }
 
-    checkThrottle()
-    wsk.api.get(basepathOrApiName, full, expectedExitCode, cliCfgFile, format)
+  def verifyApiOp(rr: RunResult, testurlop: String, testapiname: String): Unit = {
+    rr.stdout should include regex (s"\\s+${testurlop}\\s+${testapiname}\\s+")
   }
 
-  def apiDelete(basepathOrApiName: String,
-                relpath: Option[String] = None,
-                operation: Option[String] = None,
-                expectedExitCode: Int = SUCCESS_EXIT,
-                cliCfgFile: Option[String] = Some(cliWskPropsFile.getCanonicalPath())): RunResult = {
+  def verifyApiOpVerb(rr: RunResult, testurlop: String): Unit = {
+    rr.stdout should include regex (s"Verb:\\s+${testurlop}")
+  }
 
-    checkThrottle()
-    wsk.api.delete(basepathOrApiName, relpath, operation, expectedExitCode, cliCfgFile)
+  def verifyInvalidKey(rr: RunResult): Unit = {
+    rr.stderr should include("The supplied authentication is invalid")
   }
 
   behavior of "Wsk api"
@@ -176,21 +174,21 @@ class ApiGwTests extends TestHelpers with WskTestHelpers with BeforeAndAfterEach
       operation = Some("GET"),
       action = Some("action"),
       expectedExitCode = ANY_ERROR_EXIT)
-    rr.stderr should include(s"'${badpath}' must begin with '/'")
+    verifyBadCommands(rr, badpath)
 
     rr = apiDelete(
       basepathOrApiName = "/basepath",
       relpath = Some(badpath),
       operation = Some("GET"),
       expectedExitCode = ANY_ERROR_EXIT)
-    rr.stderr should include(s"'${badpath}' must begin with '/'")
+    verifyBadCommandsDelete(rr, badpath)
 
     rr = apiList(
       basepathOrApiName = Some("/basepath"),
       relpath = Some(badpath),
       operation = Some("GET"),
       expectedExitCode = ANY_ERROR_EXIT)
-    rr.stderr should include(s"'${badpath}' must begin with '/'")
+    verifyBadCommandsList(rr, badpath)
   }
 
   it should "reject an api commands with an invalid verb parameter" in {
@@ -202,28 +200,28 @@ class ApiGwTests extends TestHelpers with WskTestHelpers with BeforeAndAfterEach
       operation = Some(badverb),
       action = Some("action"),
       expectedExitCode = ANY_ERROR_EXIT)
-    rr.stderr should include(s"'${badverb}' is not a valid API verb.  Valid values are:")
+    verifyInvalidCommands(rr, badverb)
 
     rr = apiDelete(
       basepathOrApiName = "/basepath",
       relpath = Some("/path"),
       operation = Some(badverb),
       expectedExitCode = ANY_ERROR_EXIT)
-    rr.stderr should include(s"'${badverb}' is not a valid API verb.  Valid values are:")
+    verifyInvalidCommandsDelete(rr, badverb)
 
     rr = apiList(
       basepathOrApiName = Some("/basepath"),
       relpath = Some("/path"),
       operation = Some(badverb),
       expectedExitCode = ANY_ERROR_EXIT)
-    rr.stderr should include(s"'${badverb}' is not a valid API verb.  Valid values are:")
+    verifyInvalidCommandsList(rr, badverb)
   }
 
   it should "reject an api create command that specifies a nonexistent configuration file" in {
     val configfile = "/nonexistent/file"
 
     val rr = apiCreate(swagger = Some(configfile), expectedExitCode = ANY_ERROR_EXIT)
-    rr.stderr should include(s"Error reading swagger file '${configfile}':")
+    rr.stderr should include(s"Error reading swagger file '${configfile}'")
   }
 
   it should "reject an api create command specifying a non-JSON configuration file" in {
@@ -236,7 +234,7 @@ class ApiGwTests extends TestHelpers with WskTestHelpers with BeforeAndAfterEach
     bw.close()
 
     val rr = apiCreate(swagger = Some(filename), expectedExitCode = ANY_ERROR_EXIT)
-    rr.stderr should include(s"Error parsing swagger file '${filename}':")
+    verifyNonJsonSwagger(rr, filename)
   }
 
   it should "reject an api create command specifying a non-swagger JSON configuration file" in {
@@ -261,7 +259,7 @@ class ApiGwTests extends TestHelpers with WskTestHelpers with BeforeAndAfterEach
     bw.close()
 
     val rr = apiCreate(swagger = Some(filename), expectedExitCode = ANY_ERROR_EXIT)
-    rr.stderr should include(s"Swagger file is invalid (missing basePath, info, paths, or swagger fields")
+    verifyMissingField(rr)
   }
 
   it should "verify full list output" in {
@@ -276,7 +274,7 @@ class ApiGwTests extends TestHelpers with WskTestHelpers with BeforeAndAfterEach
       println("cli namespace: " + clinamespace)
       // Create the action for the API.  It must be a "web-action" action.
       val file = TestCLIUtils.getTestActionFilename(s"echo.js")
-      wsk.action.create(name = actionName, artifact = Some(file), expectedExitCode = SUCCESS_EXIT, web = Some("true"))
+      wsk.action.create(name = actionName, artifact = Some(file), expectedExitCode = createCode, web = Some("true"))
 
       var rr = apiCreate(
         basepath = Some(testbasepath),
@@ -285,21 +283,14 @@ class ApiGwTests extends TestHelpers with WskTestHelpers with BeforeAndAfterEach
         action = Some(actionName),
         apiname = Some(testapiname))
       println("api create: " + rr.stdout)
-      rr.stdout should include("ok: created API")
+      verifyApiCreated(rr)
       rr = apiList(
         basepathOrApiName = Some(testbasepath),
         relpath = Some(testrelpath),
         operation = Some(testurlop),
         full = Some(true))
       println("api list: " + rr.stdout)
-      rr.stdout should include("ok: APIs")
-      rr.stdout should include regex (s"Action:\\s+/${clinamespace}/${actionName}\n")
-      rr.stdout should include regex (s"Verb:\\s+${testurlop}\n")
-      rr.stdout should include regex (s"Base path:\\s+${testbasepath}\n")
-      rr.stdout should include regex (s"Path:\\s+${testrelpath}\n")
-      rr.stdout should include regex (s"API Name:\\s+${testapiname}\n")
-      rr.stdout should include regex (s"URL:\\s+")
-      rr.stdout should include(testbasepath + testrelpath)
+      verifyApiList(rr, clinamespace, actionName, testurlop, testbasepath, testrelpath, testapiname)
     } finally {
       wsk.action.delete(name = actionName, expectedExitCode = DONTCARE_EXIT)
       apiDelete(basepathOrApiName = testbasepath)
@@ -319,7 +310,7 @@ class ApiGwTests extends TestHelpers with WskTestHelpers with BeforeAndAfterEach
 
       // Create the action for the API.  It must be a "web-action" action.
       val file = TestCLIUtils.getTestActionFilename(s"echo.js")
-      wsk.action.create(name = actionName, artifact = Some(file), expectedExitCode = SUCCESS_EXIT, web = Some("true"))
+      wsk.action.create(name = actionName, artifact = Some(file), expectedExitCode = createCode, web = Some("true"))
 
       var rr = apiCreate(
         basepath = Some(testbasepath),
@@ -327,15 +318,13 @@ class ApiGwTests extends TestHelpers with WskTestHelpers with BeforeAndAfterEach
         operation = Some(testurlop),
         action = Some(actionName),
         apiname = Some(testapiname))
-      rr.stdout should include("ok: created API")
+      verifyApiCreated(rr)
       rr = apiList(basepathOrApiName = Some(testbasepath), relpath = Some(testrelpath), operation = Some(testurlop))
-      rr.stdout should include("ok: APIs")
-      rr.stdout should include regex (s"/${clinamespace}/${actionName}\\s+${testurlop}\\s+${testapiname}\\s+")
-      rr.stdout should include(testbasepath + testrelpath)
+      verifyApiFullList(rr, clinamespace, actionName, testurlop, testbasepath, testrelpath, testapiname)
       rr = apiGet(basepathOrApiName = Some(testbasepath))
-      rr.stdout should include regex (s""""operationId":\\s+"getPathWithSub_pathsInIt"""")
+      verifyApiGet(rr)
       val deleteresult = apiDelete(basepathOrApiName = testbasepath)
-      deleteresult.stdout should include("ok: deleted API")
+      verifyApiDeleted(deleteresult)
     } finally {
       wsk.action.delete(name = actionName, expectedExitCode = DONTCARE_EXIT)
       apiDelete(basepathOrApiName = testbasepath, expectedExitCode = DONTCARE_EXIT)
@@ -353,7 +342,7 @@ class ApiGwTests extends TestHelpers with WskTestHelpers with BeforeAndAfterEach
     try {
       // Create the action for the API.  It must be a "web-action" action.
       val file = TestCLIUtils.getTestActionFilename(s"echo.js")
-      wsk.action.create(name = actionName, artifact = Some(file), expectedExitCode = SUCCESS_EXIT, web = Some("true"))
+      wsk.action.create(name = actionName, artifact = Some(file), expectedExitCode = createCode, web = Some("true"))
 
       var rr = apiCreate(
         basepath = Some(testbasepath),
@@ -361,12 +350,9 @@ class ApiGwTests extends TestHelpers with WskTestHelpers with BeforeAndAfterEach
         operation = Some(testurlop),
         action = Some(actionName),
         apiname = Some(testapiname))
-      rr.stdout should include("ok: created API")
+      verifyApiCreated(rr)
       rr = apiGet(basepathOrApiName = Some(testapiname))
-      rr.stdout should include(testbasepath)
-      rr.stdout should include(s"${actionName}")
-      rr.stdout should include regex (""""cors":\s*\{\s*\n\s*"enabled":\s*true""")
-      rr.stdout should include regex (s""""target-url":\\s+.*${actionName}.json""")
+      verifyApiNameGet(rr, testbasepath, actionName)
     } finally {
       wsk.action.delete(name = actionName, expectedExitCode = DONTCARE_EXIT)
       apiDelete(basepathOrApiName = testbasepath, expectedExitCode = DONTCARE_EXIT)
@@ -384,7 +370,7 @@ class ApiGwTests extends TestHelpers with WskTestHelpers with BeforeAndAfterEach
     try {
       // Create the action for the API.  It must be a "web-action" action.
       val file = TestCLIUtils.getTestActionFilename(s"echo.js")
-      wsk.action.create(name = actionName, artifact = Some(file), expectedExitCode = SUCCESS_EXIT, web = Some("true"))
+      wsk.action.create(name = actionName, artifact = Some(file), expectedExitCode = createCode, web = Some("true"))
 
       var rr = apiCreate(
         basepath = Some(testbasepath),
@@ -392,9 +378,9 @@ class ApiGwTests extends TestHelpers with WskTestHelpers with BeforeAndAfterEach
         operation = Some(testurlop),
         action = Some(actionName),
         apiname = Some(testapiname))
-      rr.stdout should include("ok: created API")
+      verifyApiCreated(rr)
       rr = apiDelete(basepathOrApiName = testapiname)
-      rr.stdout should include("ok: deleted API")
+      verifyApiDeleted(rr)
     } finally {
       wsk.action.delete(name = actionName, expectedExitCode = DONTCARE_EXIT)
       apiDelete(basepathOrApiName = testbasepath, expectedExitCode = DONTCARE_EXIT)
@@ -412,7 +398,7 @@ class ApiGwTests extends TestHelpers with WskTestHelpers with BeforeAndAfterEach
     try {
       // Create the action for the API.  It must be a "web-action" action.
       val file = TestCLIUtils.getTestActionFilename(s"echo.js")
-      wsk.action.create(name = actionName, artifact = Some(file), expectedExitCode = SUCCESS_EXIT, web = Some("true"))
+      wsk.action.create(name = actionName, artifact = Some(file), expectedExitCode = createCode, web = Some("true"))
 
       var rr = apiCreate(
         basepath = Some(testbasepath),
@@ -420,9 +406,9 @@ class ApiGwTests extends TestHelpers with WskTestHelpers with BeforeAndAfterEach
         operation = Some(testurlop),
         action = Some(actionName),
         apiname = Some(testapiname))
-      rr.stdout should include("ok: created API")
+      verifyApiCreated(rr)
       rr = apiDelete(basepathOrApiName = testbasepath)
-      rr.stdout should include("ok: deleted API")
+      verifyApiDeleted(rr)
     } finally {
       wsk.action.delete(name = actionName, expectedExitCode = DONTCARE_EXIT)
       apiDelete(basepathOrApiName = testbasepath, expectedExitCode = DONTCARE_EXIT)
@@ -441,7 +427,7 @@ class ApiGwTests extends TestHelpers with WskTestHelpers with BeforeAndAfterEach
     try {
       // Create the action for the API.  It must be a "web-action" action.
       val file = TestCLIUtils.getTestActionFilename(s"echo.js")
-      wsk.action.create(name = actionName, artifact = Some(file), expectedExitCode = SUCCESS_EXIT, web = Some("true"))
+      wsk.action.create(name = actionName, artifact = Some(file), expectedExitCode = createCode, web = Some("true"))
 
       var rr = apiCreate(
         basepath = Some(testbasepath),
@@ -449,19 +435,24 @@ class ApiGwTests extends TestHelpers with WskTestHelpers with BeforeAndAfterEach
         operation = Some(testurlop),
         action = Some(actionName),
         apiname = Some(testapiname))
-      rr.stdout should include("ok: created API")
+      verifyApiCreated(rr)
       rr = apiCreate(
         basepath = Some(testbasepath),
         relpath = Some(newEndpoint),
         operation = Some(testurlop),
         action = Some(actionName),
         apiname = Some(testapiname))
-      rr.stdout should include("ok: created API")
+      verifyApiCreated(rr)
       rr = apiList(basepathOrApiName = Some(testbasepath))
-      rr.stdout should include("ok: APIs")
-      rr.stdout should include regex (s"/${clinamespace}/${actionName}\\s+${testurlop}\\s+${testapiname}\\s+")
-      rr.stdout should include(testbasepath + testrelpath)
-      rr.stdout should include(testbasepath + newEndpoint)
+      verifyApiFullListDouble(
+        rr,
+        clinamespace,
+        actionName,
+        testurlop,
+        testbasepath,
+        newEndpoint,
+        testapiname,
+        newEndpoint)
     } finally {
       wsk.action.delete(name = actionName, expectedExitCode = DONTCARE_EXIT)
       apiDelete(basepathOrApiName = testbasepath, expectedExitCode = DONTCARE_EXIT)
@@ -479,14 +470,12 @@ class ApiGwTests extends TestHelpers with WskTestHelpers with BeforeAndAfterEach
     val swaggerPath = TestCLIUtils.getTestApiGwFilename("testswaggerdoc1")
     try {
       var rr = apiCreate(swagger = Some(swaggerPath))
-      rr.stdout should include("ok: created API")
+      verifyApiCreated(rr)
       rr = apiList(basepathOrApiName = Some(testbasepath), relpath = Some(testrelpath), operation = Some(testurlop))
       println("list stdout: " + rr.stdout)
       println("list stderr: " + rr.stderr)
-      rr.stdout should include("ok: APIs")
-      // Actual CLI namespace will vary from local dev to automated test environments, so don't check
-      rr.stdout should include regex (s"/[@\\w._\\-]+/${actionName}\\s+${testurlop}\\s+${testapiname}\\s+")
-      rr.stdout should include(testbasepath + testrelpath)
+      verifyApiFullList(rr, "", actionName, testurlop, testbasepath, testrelpath, testapiname)
+
     } finally {
       apiDelete(basepathOrApiName = testbasepath, expectedExitCode = DONTCARE_EXIT)
     }
@@ -506,7 +495,7 @@ class ApiGwTests extends TestHelpers with WskTestHelpers with BeforeAndAfterEach
     try {
       // Create the action for the API.  It must be a "web-action" action.
       val file = TestCLIUtils.getTestActionFilename(s"echo.js")
-      wsk.action.create(name = actionName, artifact = Some(file), expectedExitCode = SUCCESS_EXIT, web = Some("true"))
+      wsk.action.create(name = actionName, artifact = Some(file), expectedExitCode = createCode, web = Some("true"))
 
       var rr = apiCreate(
         basepath = Some(testbasepath),
@@ -514,14 +503,14 @@ class ApiGwTests extends TestHelpers with WskTestHelpers with BeforeAndAfterEach
         operation = Some(testurlop),
         action = Some(actionName),
         apiname = Some(testapiname))
-      rr.stdout should include("ok: created API")
+      verifyApiCreated(rr)
       rr = apiCreate(
         basepath = Some(testbasepath2),
         relpath = Some(testrelpath),
         operation = Some(testurlop),
         action = Some(actionName),
         apiname = Some(testapiname2))
-      rr.stdout should include("ok: created API")
+      verifyApiCreated(rr)
 
       // Update both APIs - each with a new endpoint
       rr = apiCreate(
@@ -529,25 +518,36 @@ class ApiGwTests extends TestHelpers with WskTestHelpers with BeforeAndAfterEach
         relpath = Some(newEndpoint),
         operation = Some(testurlop),
         action = Some(actionName))
-      rr.stdout should include("ok: created API")
+      verifyApiCreated(rr)
       rr = apiCreate(
         basepath = Some(testbasepath2),
         relpath = Some(newEndpoint),
         operation = Some(testurlop),
         action = Some(actionName))
-      rr.stdout should include("ok: created API")
+      verifyApiCreated(rr)
 
       rr = apiList(basepathOrApiName = Some(testbasepath))
-      rr.stdout should include("ok: APIs")
-      rr.stdout should include regex (s"/${clinamespace}/${actionName}\\s+${testurlop}\\s+${testapiname}\\s+")
-      rr.stdout should include(testbasepath + testrelpath)
-      rr.stdout should include(testbasepath + newEndpoint)
+      verifyApiFullListDouble(
+        rr,
+        clinamespace,
+        actionName,
+        testurlop,
+        testbasepath,
+        testrelpath,
+        testapiname,
+        newEndpoint)
 
       rr = apiList(basepathOrApiName = Some(testbasepath2))
-      rr.stdout should include("ok: APIs")
-      rr.stdout should include regex (s"/${clinamespace}/${actionName}\\s+${testurlop}\\s+${testapiname}\\s+")
-      rr.stdout should include(testbasepath2 + testrelpath)
-      rr.stdout should include(testbasepath2 + newEndpoint)
+      verifyApiFullListDouble(
+        rr,
+        clinamespace,
+        actionName,
+        testurlop,
+        testbasepath2,
+        testrelpath,
+        testapiname2,
+        newEndpoint)
+
     } finally {
       wsk.action.delete(name = actionName, expectedExitCode = DONTCARE_EXIT)
       apiDelete(basepathOrApiName = testbasepath, expectedExitCode = DONTCARE_EXIT)
@@ -570,7 +570,7 @@ class ApiGwTests extends TestHelpers with WskTestHelpers with BeforeAndAfterEach
 
       // Create the action for the API.  It must be a "web-action" action.
       val file = TestCLIUtils.getTestActionFilename(s"echo.js")
-      wsk.action.create(name = actionName, artifact = Some(file), expectedExitCode = SUCCESS_EXIT, web = Some("true"))
+      wsk.action.create(name = actionName, artifact = Some(file), expectedExitCode = createCode, web = Some("true"))
 
       var rr = apiCreate(
         basepath = Some(testbasepath),
@@ -578,13 +578,11 @@ class ApiGwTests extends TestHelpers with WskTestHelpers with BeforeAndAfterEach
         operation = Some(testurlop),
         action = Some(actionName),
         apiname = Some(testapiname))
-      rr.stdout should include("ok: created API")
+      verifyApiCreated(rr)
       rr = apiList(basepathOrApiName = Some(testbasepath), relpath = Some(testrelpath), operation = Some(testurlop))
-      rr.stdout should include("ok: APIs")
-      rr.stdout should include regex (s"/${clinamespace}/${actionName}\\s+${testurlop}\\s+${testapiname}\\s+")
-      rr.stdout should include(testbasepath + testrelpath)
+      verifyApiFullList(rr, clinamespace, actionName, testurlop, testbasepath, testrelpath, testapiname)
       val deleteresult = apiDelete(basepathOrApiName = testbasepath)
-      deleteresult.stdout should include("ok: deleted API")
+      verifyApiDeleted(deleteresult)
     } finally {
       wsk.action.delete(name = actionName, expectedExitCode = DONTCARE_EXIT)
       apiDelete(basepathOrApiName = testbasepath, expectedExitCode = DONTCARE_EXIT)
@@ -604,7 +602,7 @@ class ApiGwTests extends TestHelpers with WskTestHelpers with BeforeAndAfterEach
       val rr = apiCreate(swagger = Some(swaggerPath), expectedExitCode = ANY_ERROR_EXIT)
       println("api create stdout: " + rr.stdout)
       println("api create stderr: " + rr.stderr)
-      rr.stderr should include(s"Swagger file is invalid")
+      verifyInvalidSwagger(rr)
     } finally {
       apiDelete(basepathOrApiName = testbasepath, expectedExitCode = DONTCARE_EXIT)
     }
@@ -621,7 +619,7 @@ class ApiGwTests extends TestHelpers with WskTestHelpers with BeforeAndAfterEach
     try {
       // Create the action for the API.  It must be a "web-action" action.
       val file = TestCLIUtils.getTestActionFilename(s"echo.js")
-      wsk.action.create(name = actionName, artifact = Some(file), expectedExitCode = SUCCESS_EXIT, web = Some("true"))
+      wsk.action.create(name = actionName, artifact = Some(file), expectedExitCode = createCode, web = Some("true"))
 
       var rr = apiCreate(
         basepath = Some(testbasepath),
@@ -629,20 +627,18 @@ class ApiGwTests extends TestHelpers with WskTestHelpers with BeforeAndAfterEach
         operation = Some(testurlop),
         action = Some(actionName),
         apiname = Some(testapiname))
-      rr.stdout should include("ok: created API")
+      verifyApiCreated(rr)
       var rr2 = apiCreate(
         basepath = Some(testbasepath),
         relpath = Some(testnewrelpath),
         operation = Some(testurlop),
         action = Some(actionName),
         apiname = Some(testapiname))
-      rr2.stdout should include("ok: created API")
+      verifyApiCreated(rr2)
       rr = apiDelete(basepathOrApiName = testbasepath, relpath = Some(testrelpath))
-      rr.stdout should include("ok: deleted " + testrelpath + " from " + testbasepath)
+      verifyApiDeletedRelpath(rr, testrelpath, testbasepath)
       rr2 = apiList(basepathOrApiName = Some(testbasepath), relpath = Some(testnewrelpath))
-      rr2.stdout should include("ok: APIs")
-      rr2.stdout should include regex (s"/${clinamespace}/${actionName}\\s+${testurlop}\\s+${testapiname}\\s+")
-      rr2.stdout should include(testbasepath + testnewrelpath)
+      verifyApiFullList(rr2, clinamespace, actionName, testurlop, testbasepath, testnewrelpath, testapiname)
     } finally {
       wsk.action.delete(name = actionName, expectedExitCode = DONTCARE_EXIT)
       apiDelete(basepathOrApiName = testbasepath, expectedExitCode = DONTCARE_EXIT)
@@ -661,7 +657,7 @@ class ApiGwTests extends TestHelpers with WskTestHelpers with BeforeAndAfterEach
     try {
       // Create the action for the API.  It must be a "web-action" action.
       val file = TestCLIUtils.getTestActionFilename(s"echo.js")
-      wsk.action.create(name = actionName, artifact = Some(file), expectedExitCode = SUCCESS_EXIT, web = Some("true"))
+      wsk.action.create(name = actionName, artifact = Some(file), expectedExitCode = createCode, web = Some("true"))
 
       var rr = apiCreate(
         basepath = Some(testbasepath),
@@ -669,22 +665,22 @@ class ApiGwTests extends TestHelpers with WskTestHelpers with BeforeAndAfterEach
         operation = Some(testurlop),
         action = Some(actionName),
         apiname = Some(testapiname))
-      rr.stdout should include("ok: created API")
+      verifyApiCreated(rr)
       rr = apiCreate(
         basepath = Some(testbasepath),
         relpath = Some(testrelpath),
         operation = Some(testurlop2),
         action = Some(actionName),
         apiname = Some(testapiname))
-      rr.stdout should include("ok: created API")
+      verifyApiCreated(rr)
       rr = apiList(basepathOrApiName = Some(testbasepath))
-      rr.stdout should include("ok: APIs")
-      rr.stdout should include regex (s"/${clinamespace}/${actionName}\\s+${testurlop}\\s+${testapiname}\\s+")
-      rr.stdout should include(testbasepath + testrelpath)
+      verifyApiFullList(rr, clinamespace, actionName, testurlop, testbasepath, testrelpath, testapiname)
+      verifyApiFullList(rr, clinamespace, actionName, testurlop2, testbasepath, testrelpath, testapiname)
       rr = apiDelete(basepathOrApiName = testbasepath, relpath = Some(testrelpath), operation = Some(testurlop2))
-      rr.stdout should include("ok: deleted " + testrelpath + " " + "POST" + " from " + testbasepath)
+      verifyApiDeletedRelpath(rr, testrelpath, testbasepath, testurlop2)
+
       rr = apiList(basepathOrApiName = Some(testbasepath))
-      rr.stdout should include regex (s"/${clinamespace}/${actionName}\\s+${testurlop}\\s+${testapiname}\\s+")
+      verifyApiFullList(rr, clinamespace, actionName, testurlop, testbasepath, testrelpath, testapiname)
     } finally {
       wsk.action.delete(name = actionName, expectedExitCode = DONTCARE_EXIT)
       apiDelete(basepathOrApiName = testbasepath, expectedExitCode = DONTCARE_EXIT)
@@ -697,6 +693,7 @@ class ApiGwTests extends TestHelpers with WskTestHelpers with BeforeAndAfterEach
     val testrelpath = "/whisk_system/utils/echo"
     val testrelpath2 = "/whisk_system/utils/split"
     val testurlop = "get"
+    val testurlop2 = "post"
     val testapiname = testName + " API Name"
     val actionName = "test1a"
     val swaggerPath = TestCLIUtils.getTestApiGwFilename(s"testswaggerdoc2")
@@ -704,13 +701,10 @@ class ApiGwTests extends TestHelpers with WskTestHelpers with BeforeAndAfterEach
       var rr = apiCreate(swagger = Some(swaggerPath))
       println("api create stdout: " + rr.stdout)
       println("api create stderror: " + rr.stderr)
-      rr.stdout should include("ok: created API")
+      verifyApiCreated(rr)
       rr = apiList(basepathOrApiName = Some(testbasepath))
-      rr.stdout should include("ok: APIs")
-      // Actual CLI namespace will vary from local dev to automated test environments, so don't check
-      rr.stdout should include regex (s"/[@\\w._\\-]+/${actionName}\\s+${testurlop}\\s+${testapiname}\\s+")
-      rr.stdout should include(testbasepath + testrelpath)
-      rr.stdout should include(testbasepath + testrelpath2)
+      verifyApiFullList(rr, "", actionName, testurlop, testbasepath, testrelpath, testapiname)
+      verifyApiFullList(rr, "", actionName, testurlop2, testbasepath, testrelpath2, testapiname)
     } finally {
       apiDelete(basepathOrApiName = testbasepath, expectedExitCode = DONTCARE_EXIT)
     }
@@ -729,7 +723,7 @@ class ApiGwTests extends TestHelpers with WskTestHelpers with BeforeAndAfterEach
     try {
       // Create the action for the API.  It must be a "web-action" action.
       val file = TestCLIUtils.getTestActionFilename(s"echo.js")
-      wsk.action.create(name = actionName, artifact = Some(file), expectedExitCode = SUCCESS_EXIT, web = Some("true"))
+      wsk.action.create(name = actionName, artifact = Some(file), expectedExitCode = createCode, web = Some("true"))
 
       var rr = apiCreate(
         basepath = Some(testbasepath),
@@ -737,30 +731,24 @@ class ApiGwTests extends TestHelpers with WskTestHelpers with BeforeAndAfterEach
         operation = Some(testurlop),
         action = Some(actionName),
         apiname = Some(testapiname))
-      rr.stdout should include("ok: created API")
+      verifyApiCreated(rr)
       rr = apiList(basepathOrApiName = Some(testbasepath), relpath = Some(testrelpath), operation = Some(testurlop))
-      rr.stdout should include("ok: APIs")
-      rr.stdout should include regex (s"/${clinamespace}/${actionName}\\s+${testurlop}\\s+${testapiname}\\s+")
-      rr.stdout should include(testbasepath + testrelpath)
+      verifyApiFullList(rr, clinamespace, actionName, testurlop, testbasepath, testrelpath, testapiname)
       rr = apiCreate(
         basepath = Some(testbasepath2),
         relpath = Some(testrelpath),
         operation = Some(testurlop),
         action = Some(actionName),
         apiname = Some(testapiname2))
-      rr.stdout should include("ok: created API")
+      verifyApiCreated(rr)
       rr = apiList(basepathOrApiName = Some(testbasepath2), relpath = Some(testrelpath), operation = Some(testurlop))
-      rr.stdout should include("ok: APIs")
-      rr.stdout should include regex (s"/${clinamespace}/${actionName}\\s+${testurlop}\\s+${testapiname}\\s+")
-      rr.stdout should include(testbasepath2 + testrelpath)
+      verifyApiFullList(rr, clinamespace, actionName, testurlop, testbasepath2, testrelpath, testapiname2)
       rr = apiDelete(basepathOrApiName = testbasepath2)
-      rr.stdout should include("ok: deleted API")
+      verifyApiDeleted(rr)
       rr = apiList(basepathOrApiName = Some(testbasepath), relpath = Some(testrelpath), operation = Some(testurlop))
-      rr.stdout should include("ok: APIs")
-      rr.stdout should include regex (s"/${clinamespace}/${actionName}\\s+${testurlop}\\s+${testapiname}\\s+")
-      rr.stdout should include(testbasepath + testrelpath)
+      verifyApiFullList(rr, clinamespace, actionName, testurlop, testbasepath, testrelpath, testapiname)
       rr = apiDelete(basepathOrApiName = testbasepath)
-      rr.stdout should include("ok: deleted API")
+      verifyApiDeleted(rr)
     } finally {
       wsk.action.delete(name = actionName, expectedExitCode = DONTCARE_EXIT)
       apiDelete(basepathOrApiName = testbasepath, expectedExitCode = DONTCARE_EXIT)
@@ -768,55 +756,6 @@ class ApiGwTests extends TestHelpers with WskTestHelpers with BeforeAndAfterEach
     }
   }
 
-  it should "reject an API created with a non-existent action" in {
-    val testName = "CLI_APIGWTEST15"
-    val testbasepath = "/" + testName + "_bp"
-    val testrelpath = "/path"
-    val testnewrelpath = "/path_new"
-    val testurlop = "get"
-    val testapiname = testName + " API Name"
-    val actionName = testName + "_action"
-    try {
-      val rr = apiCreate(
-        basepath = Some(testbasepath),
-        relpath = Some(testrelpath),
-        operation = Some(testurlop),
-        action = Some(actionName),
-        apiname = Some(testapiname),
-        expectedExitCode = ANY_ERROR_EXIT)
-      rr.stderr should include("does not exist")
-    } finally {
-      apiDelete(basepathOrApiName = testbasepath, expectedExitCode = DONTCARE_EXIT)
-    }
-  }
-
-  it should "reject an API created with an action that is not a web action" in {
-    val testName = "CLI_APIGWTEST16"
-    val testbasepath = "/" + testName + "_bp"
-    val testrelpath = "/path"
-    val testnewrelpath = "/path_new"
-    val testurlop = "get"
-    val testapiname = testName + " API Name"
-    val actionName = testName + "_action"
-    try {
-      // Create the action for the API.  It must NOT be a "web-action" action for this test
-      val file = TestCLIUtils.getTestActionFilename(s"echo.js")
-      wsk.action.create(name = actionName, artifact = Some(file), expectedExitCode = SUCCESS_EXIT)
-
-      val rr = apiCreate(
-        basepath = Some(testbasepath),
-        relpath = Some(testrelpath),
-        operation = Some(testurlop),
-        action = Some(actionName),
-        apiname = Some(testapiname),
-        expectedExitCode = ANY_ERROR_EXIT)
-      rr.stderr should include("is not a web action")
-    } finally {
-      wsk.action.delete(name = actionName, expectedExitCode = DONTCARE_EXIT)
-      apiDelete(basepathOrApiName = testbasepath, expectedExitCode = DONTCARE_EXIT)
-    }
-  }
-
   it should "verify API with http response type " in {
     val testName = "CLI_APIGWTEST17"
     val testbasepath = "/" + testName + "_bp"
@@ -829,133 +768,25 @@ class ApiGwTests extends TestHelpers with WskTestHelpers with BeforeAndAfterEach
     try {
       // Create the action for the API.  It must be a "web-action" action.
       val file = TestCLIUtils.getTestActionFilename(s"echo.js")
-      wsk.action.create(name = actionName, artifact = Some(file), expectedExitCode = SUCCESS_EXIT, web = Some("true"))
-
-      apiCreate(
-        basepath = Some(testbasepath),
-        relpath = Some(testrelpath),
-        operation = Some(testurlop),
-        action = Some(actionName),
-        apiname = Some(testapiname),
-        responsetype = Some(responseType)).stdout should include("ok: created API")
-
-      val rr = apiGet(basepathOrApiName = Some(testapiname))
-      rr.stdout should include(testbasepath)
-      rr.stdout should include(s"${actionName}")
-      rr.stdout should include regex (s""""target-url":\\s+.*${actionName}.${responseType}""")
-    } finally {
-      wsk.action.delete(name = actionName, expectedExitCode = DONTCARE_EXIT)
-      apiDelete(basepathOrApiName = testbasepath, expectedExitCode = DONTCARE_EXIT)
-    }
-  }
-
-  it should "reject API export when export type is invalid" in {
-    val testName = "CLI_APIGWTEST18"
-    val testbasepath = "/" + testName + "_bp"
-
-    val rr = apiGet(basepathOrApiName = Some(testbasepath), format = Some("BadType"), expectedExitCode = ANY_ERROR_EXIT)
-    rr.stderr should include("Invalid format type")
-  }
-
-  it should "successfully export an API in YAML format" in {
-    val testName = "CLI_APIGWTEST19"
-    val testbasepath = "/" + testName + "_bp"
-    val testrelpath = "/path"
-    val testnewrelpath = "/path_new"
-    val testurlop = "get"
-    val testapiname = testName + " API Name"
-    val actionName = testName + "_action"
-    val responseType = "http"
-    try {
-      // Create the action for the API.  It must be a "web-action" action.
-      val file = TestCLIUtils.getTestActionFilename(s"echo.js")
-      wsk.action.create(name = actionName, artifact = Some(file), expectedExitCode = SUCCESS_EXIT, web = Some("true"))
+      wsk.action.create(name = actionName, artifact = Some(file), expectedExitCode = createCode, web = Some("true"))
 
-      apiCreate(
-        basepath = Some(testbasepath),
-        relpath = Some(testrelpath),
-        operation = Some(testurlop),
-        action = Some(actionName),
-        apiname = Some(testapiname),
-        responsetype = Some(responseType)).stdout should include("ok: created API")
-
-      val rr = apiGet(basepathOrApiName = Some(testapiname), format = Some("yaml"))
-      rr.stdout should include(s"basePath: ${testbasepath}")
-    } finally {
-      wsk.action.delete(name = actionName, expectedExitCode = DONTCARE_EXIT)
-      apiDelete(basepathOrApiName = testbasepath, expectedExitCode = DONTCARE_EXIT)
-    }
-  }
-
-  it should "successfully export an API when JSON format is explcitly specified" in {
-    val testName = "CLI_APIGWTEST20"
-    val testbasepath = "/" + testName + "_bp"
-    val testrelpath = "/path"
-    val testnewrelpath = "/path_new"
-    val testurlop = "get"
-    val testapiname = testName + " API Name"
-    val actionName = testName + "_action"
-    val responseType = "http"
-    try {
-      // Create the action for the API.  It must be a "web-action" action.
-      val file = TestCLIUtils.getTestActionFilename(s"echo.js")
-      wsk.action.create(name = actionName, artifact = Some(file), expectedExitCode = SUCCESS_EXIT, web = Some("true"))
-
-      apiCreate(
+      var rr = apiCreate(
         basepath = Some(testbasepath),
         relpath = Some(testrelpath),
         operation = Some(testurlop),
         action = Some(actionName),
         apiname = Some(testapiname),
-        responsetype = Some(responseType)).stdout should include("ok: created API")
+        responsetype = Some(responseType))
+      verifyApiCreated(rr)
 
-      val rr = apiGet(basepathOrApiName = Some(testapiname), format = Some("json"))
-      rr.stdout should include(testbasepath)
-      rr.stdout should include(s"${actionName}")
-      rr.stdout should include regex (s""""target-url":\\s+.*${actionName}.${responseType}""")
+      rr = apiGet(basepathOrApiName = Some(testapiname))
+      verifyApiNameGet(rr, testbasepath, actionName, responseType)
     } finally {
       wsk.action.delete(name = actionName, expectedExitCode = DONTCARE_EXIT)
       apiDelete(basepathOrApiName = testbasepath, expectedExitCode = DONTCARE_EXIT)
     }
   }
 
-  it should "successfully create an API from a YAML formatted API configuration file" in {
-    val testName = "CLI_APIGWTEST21"
-    val testbasepath = "/bp"
-    val testrelpath = "/rp"
-    val testurlop = "get"
-    val testapiname = testbasepath
-    val actionName = "webhttpecho"
-    val swaggerPath = TestCLIUtils.getTestApiGwFilename(s"local.api.yaml")
-    try {
-      var rr = apiCreate(swagger = Some(swaggerPath))
-      println("api create stdout: " + rr.stdout)
-      println("api create stderror: " + rr.stderr)
-      rr.stdout should include("ok: created API")
-      rr = apiList(basepathOrApiName = Some(testbasepath))
-      rr.stdout should include("ok: APIs")
-      // Actual CLI namespace will vary from local dev to automated test environments, so don't check
-      rr.stdout should include regex (s"/[@\\w._\\-]+/${actionName}\\s+${testurlop}\\s+${testapiname}\\s+")
-      rr.stdout should include(testbasepath + testrelpath)
-    } finally {
-      apiDelete(basepathOrApiName = testbasepath, expectedExitCode = DONTCARE_EXIT)
-    }
-  }
-
-  it should "reject creation of an API from invalid YAML formatted API configuration file" in {
-    val testName = "CLI_APIGWTEST22"
-    val testbasepath = "/" + testName + "_bp"
-    val swaggerPath = TestCLIUtils.getTestApiGwFilename(s"local.api.bad.yaml")
-    try {
-      val rr = apiCreate(swagger = Some(swaggerPath), expectedExitCode = ANY_ERROR_EXIT)
-      println("api create stdout: " + rr.stdout)
-      println("api create stderror: " + rr.stderr)
-      rr.stderr should include("Unable to parse YAML configuration file")
-    } finally {
-      apiDelete(basepathOrApiName = testbasepath, expectedExitCode = DONTCARE_EXIT)
-    }
-  }
-
   it should "reject deletion of a non-existent api" in {
     val nonexistentApi = "/not-there"
 
@@ -975,21 +806,21 @@ class ApiGwTests extends TestHelpers with WskTestHelpers with BeforeAndAfterEach
       var rr = apiCreate(swagger = Some(swaggerPath))
       println("api create stdout: " + rr.stdout)
       println("api create stderror: " + rr.stderr)
-      rr.stdout should include("ok: created API")
+      this.verifyApiCreated(rr)
 
       rr = apiList(basepathOrApiName = Some(testbasepath))
       println("api list:\n" + rr.stdout)
       testops foreach { testurlop =>
-        rr.stdout should include regex (s"\\s+${testurlop}\\s+${testapiname}\\s+")
+        verifyApiOp(rr, testurlop, testapiname)
       }
-      rr.stdout should include(testbasepath + testrelpath)
+      verifyApiBaseRelPath(rr, testbasepath, testrelpath)
 
       rr = apiList(basepathOrApiName = Some(testbasepath), full = Some(true))
       println("api full list:\n" + rr.stdout)
       testops foreach { testurlop =>
-        rr.stdout should include regex (s"Verb:\\s+${testurlop}")
+        verifyApiOpVerb(rr, testurlop)
       }
-      rr.stdout should include(testbasepath + testrelpath)
+      verifyApiBaseRelPath(rr, testbasepath, testrelpath)
 
     } finally {
       apiDelete(basepathOrApiName = testbasepath, expectedExitCode = DONTCARE_EXIT)
@@ -1007,58 +838,22 @@ class ApiGwTests extends TestHelpers with WskTestHelpers with BeforeAndAfterEach
     try {
       // Create the action for the API.
       val file = TestCLIUtils.getTestActionFilename(s"echo.js")
-      wsk.action.create(name = actionName, artifact = Some(file), expectedExitCode = SUCCESS_EXIT, web = Some("true"))
+      wsk.action.create(name = actionName, artifact = Some(file), expectedExitCode = createCode, web = Some("true"))
 
       // Set an invalid auth key
       val badWskProps = WskProps(authKey = "bad-auth-key")
 
-      apiCreate(
+      val rr = apiCreate(
         basepath = Some(testbasepath),
         relpath = Some(testrelpath),
         operation = Some(testurlop),
         action = Some(actionName),
         apiname = Some(testapiname),
-        expectedExitCode = ANY_ERROR_EXIT)(badWskProps).stderr should include("The supplied authentication is invalid")
+        expectedExitCode = ANY_ERROR_EXIT)(badWskProps)
+      verifyInvalidKey(rr)
     } finally {
       wsk.action.delete(name = actionName, expectedExitCode = DONTCARE_EXIT)
       apiDelete(basepathOrApiName = testbasepath, expectedExitCode = DONTCARE_EXIT)
     }
   }
-
-  it should "list api alphabetically by Base/Rel/Verb" in {
-    val baseName = "/BaseTestPathApiList"
-    val actionName = "actionName"
-    val file = TestCLIUtils.getTestActionFilename(s"echo-web-http.js")
-    try {
-      // Create Action for apis
-      var action =
-        wsk.action.create(name = actionName, artifact = Some(file), expectedExitCode = SUCCESS_EXIT, web = Some("true"))
-      println("action creation: " + action.stdout)
-      // Create apis
-      for (i <- 1 to 3) {
-        val base = s"$baseName$i"
-        var api = apiCreate(
-          basepath = Some(base),
-          relpath = Some("/relPath"),
-          operation = Some("GET"),
-          action = Some(actionName))
-        println("api creation: " + api.stdout)
-      }
-      val original = apiList(nameSort = Some(true)).stdout
-      val originalFull = apiList(full = Some(true), nameSort = Some(true)).stdout
-      val scalaSorted = List(s"${baseName}1" + "/", s"${baseName}2" + "/", s"${baseName}3" + "/")
-      val regex = s"${baseName}[1-3]/".r
-      val list = (regex.findAllMatchIn(original)).toList
-      val listFull = (regex.findAllMatchIn(originalFull)).toList
-
-      scalaSorted.toString shouldEqual list.toString
-      scalaSorted.toString shouldEqual listFull.toString
-    } finally {
-      // Clean up Apis
-      for (i <- 1 to 3) {
-        apiDelete(basepathOrApiName = s"${baseName}$i", expectedExitCode = DONTCARE_EXIT)
-      }
-      wsk.action.delete(name = actionName, expectedExitCode = DONTCARE_EXIT)
-    }
-  }
 }
diff --git a/tests/src/test/scala/whisk/core/cli/test/BaseApiGwTests.scala b/tests/src/test/scala/whisk/core/cli/test/BaseApiGwTests.scala
new file mode 100644
index 00000000..8aa24fcf
--- /dev/null
+++ b/tests/src/test/scala/whisk/core/cli/test/BaseApiGwTests.scala
@@ -0,0 +1,156 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package whisk.core.cli.test
+
+import java.io.File
+import java.time.Instant
+
+import scala.collection.mutable.ArrayBuffer
+import scala.concurrent.duration._
+import scala.math.max
+
+import org.junit.runner.RunWith
+
+import org.scalatest.BeforeAndAfterAll
+import org.scalatest.BeforeAndAfterEach
+import org.scalatest.junit.JUnitRunner
+
+import common.TestHelpers
+import common.TestUtils._
+import common.WhiskProperties
+import common.BaseWsk
+import common.WskProps
+import common.WskTestHelpers
+
+/**
+ * Tests for testing the CLI "api" subcommand.  Most of these tests require a deployed backend.
+ */
+@RunWith(classOf[JUnitRunner])
+abstract class BaseApiGwTests extends TestHelpers with WskTestHelpers with BeforeAndAfterEach with BeforeAndAfterAll {
+
+  implicit val wskprops = WskProps()
+  val wsk: BaseWsk
+
+  // This test suite makes enough CLI invocations in 60 seconds to trigger the OpenWhisk
+  // throttling restriction.  To avoid CLI failures due to being throttled, track the
+  // CLI invocation calls and when at the throttle limit, pause the next CLI invocation
+  // with exactly enough time to relax the throttling.
+  val maxActionsPerMin = WhiskProperties.getMaxActionInvokesPerMinute()
+  val invocationTimes = new ArrayBuffer[Instant]()
+
+  // Custom CLI properties file
+  val cliWskPropsFile = File.createTempFile("wskprops", ".tmp")
+
+  /**
+   * Expected to be called before each action invocation to
+   * settle the throttle when there isn't enough capacity to handle the test.
+   */
+  def checkThrottle(maxInvocationsBeforeThrottle: Int = maxActionsPerMin, throttlePercent: Int = 50) = {
+    val t = Instant.now
+    val tminus60 = t.minusSeconds(60)
+    val invocationsLast60Seconds = invocationTimes.filter(_.isAfter(tminus60)).sorted
+    val invocationCount = invocationsLast60Seconds.length
+    println(s"Action invokes within last minute: ${invocationCount}")
+
+    if (invocationCount >= maxInvocationsBeforeThrottle && throttlePercent >= 1) {
+      val numInvocationsToClear = max(invocationCount / (100 / throttlePercent), 1)
+      val invocationToClear = invocationsLast60Seconds(numInvocationsToClear - 1)
+      println(
+        s"throttling ${throttlePercent}% of action invocations within last minute = ($numInvocationsToClear) invocations")
+      val throttleTime = 60.seconds.toMillis - (t.toEpochMilli - invocationToClear.toEpochMilli)
+
+      println(s"Waiting ${throttleTime} milliseconds to settle the throttle")
+      Thread.sleep(throttleTime)
+    }
+
+    invocationTimes += Instant.now
+  }
+
+  override def beforeEach() = {
+    //checkThrottle()
+  }
+
+  /*
+   * Create a CLI properties file for use by the tests
+   */
+  override def beforeAll() = {
+    cliWskPropsFile.deleteOnExit()
+    val wskprops = WskProps(token = "SOME TOKEN")
+    wskprops.writeFile(cliWskPropsFile)
+    println(s"wsk temporary props file created here: ${cliWskPropsFile.getCanonicalPath()}")
+  }
+
+  /*
+   * Forcibly clear the throttle so that downstream tests are not affected by
+   * this test suite
+   */
+  override def afterAll() = {
+    // Check and settle the throttle so that this test won't cause issues with any follow on tests
+    checkThrottle(maxInvocationsBeforeThrottle = 1, throttlePercent = 100)
+  }
+
+  def apiCreate(basepath: Option[String] = None,
+                relpath: Option[String] = None,
+                operation: Option[String] = None,
+                action: Option[String] = None,
+                apiname: Option[String] = None,
+                swagger: Option[String] = None,
+                responsetype: Option[String] = None,
+                expectedExitCode: Int = SUCCESS_EXIT,
+                cliCfgFile: Option[String] = Some(cliWskPropsFile.getCanonicalPath()))(
+    implicit wskpropsOverride: WskProps): RunResult = {
+
+    checkThrottle()
+    wsk.api.create(basepath, relpath, operation, action, apiname, swagger, responsetype, expectedExitCode, cliCfgFile)(
+      wskpropsOverride)
+  }
+
+  def apiList(basepathOrApiName: Option[String] = None,
+              relpath: Option[String] = None,
+              operation: Option[String] = None,
+              limit: Option[Int] = None,
+              since: Option[Instant] = None,
+              full: Option[Boolean] = None,
+              nameSort: Option[Boolean] = None,
+              expectedExitCode: Int = SUCCESS_EXIT,
+              cliCfgFile: Option[String] = Some(cliWskPropsFile.getCanonicalPath())): RunResult = {
+
+    checkThrottle()
+    wsk.api.list(basepathOrApiName, relpath, operation, limit, since, full, nameSort, expectedExitCode, cliCfgFile)
+  }
+
+  def apiGet(basepathOrApiName: Option[String] = None,
+             full: Option[Boolean] = None,
+             expectedExitCode: Int = SUCCESS_EXIT,
+             cliCfgFile: Option[String] = Some(cliWskPropsFile.getCanonicalPath()),
+             format: Option[String] = None): RunResult = {
+
+    checkThrottle()
+    wsk.api.get(basepathOrApiName, full, expectedExitCode, cliCfgFile, format)
+  }
+
+  def apiDelete(basepathOrApiName: String,
+                relpath: Option[String] = None,
+                operation: Option[String] = None,
+                expectedExitCode: Int = SUCCESS_EXIT,
+                cliCfgFile: Option[String] = Some(cliWskPropsFile.getCanonicalPath())): RunResult = {
+
+    checkThrottle()
+    wsk.api.delete(basepathOrApiName, relpath, operation, expectedExitCode, cliCfgFile)
+  }
+}
diff --git a/tests/src/test/scala/whisk/core/cli/test/JsonArgsForTests.scala b/tests/src/test/scala/whisk/core/cli/test/JsonArgsForTests.scala
new file mode 100644
index 00000000..9d8910ef
--- /dev/null
+++ b/tests/src/test/scala/whisk/core/cli/test/JsonArgsForTests.scala
@@ -0,0 +1,107 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package whisk.core.cli.test
+
+import spray.json.JsObject
+import spray.json.JsArray
+import spray.json.JsString
+import spray.json.JsNumber
+import spray.json.JsBoolean
+
+object JsonArgsForTests {
+
+  def getInvalidJSONInput =
+    Seq(
+      "{\"invalid1\": }",
+      "{\"invalid2\": bogus}",
+      "{\"invalid1\": \"aKey\"",
+      "invalid \"string\"",
+      "{\"invalid1\": [1, 2, \"invalid\"\"arr\"]}")
+
+  def getJSONFileOutput() =
+    JsArray(
+      JsObject("key" -> JsString("a key"), "value" -> JsString("a value")),
+      JsObject("key" -> JsString("a bool"), "value" -> JsBoolean(true)),
+      JsObject("key" -> JsString("objKey"), "value" -> JsObject("b" -> JsString("c"))),
+      JsObject(
+        "key" -> JsString("objKey2"),
+        "value" -> JsObject("another object" -> JsObject("some string" -> JsString("1111")))),
+      JsObject(
+        "key" -> JsString("objKey3"),
+        "value" -> JsObject("json object" -> JsObject("some int" -> JsNumber(1111)))),
+      JsObject("key" -> JsString("a number arr"), "value" -> JsArray(JsNumber(1), JsNumber(2), JsNumber(3))),
+      JsObject("key" -> JsString("a string arr"), "value" -> JsArray(JsString("1"), JsString("2"), JsString("3"))),
+      JsObject("key" -> JsString("a bool arr"), "value" -> JsArray(JsBoolean(true), JsBoolean(false), JsBoolean(true))),
+      JsObject("key" -> JsString("strThatLooksLikeJSON"), "value" -> JsString("{\"someKey\": \"someValue\"}")))
+
+  def getEscapedJSONTestArgInput() =
+    Map(
+      "key1" -> JsObject("nonascii" -> JsString("???")),
+      "key2" -> JsObject("valid" -> JsString("J\\SO\"N")),
+      "\"key\"with\\escapes" -> JsObject("valid" -> JsString("JSON")),
+      "another\"escape\"" -> JsObject("valid" -> JsString("\\nJ\\rO\\tS\\bN\\f")))
+
+  def getEscapedJSONTestArgOutput() =
+    JsArray(
+      JsObject("key" -> JsString("key1"), "value" -> JsObject("nonascii" -> JsString("???"))),
+      JsObject("key" -> JsString("key2"), "value" -> JsObject("valid" -> JsString("J\\SO\"N"))),
+      JsObject("key" -> JsString("\"key\"with\\escapes"), "value" -> JsObject("valid" -> JsString("JSON"))),
+      JsObject("key" -> JsString("another\"escape\""), "value" -> JsObject("valid" -> JsString("\\nJ\\rO\\tS\\bN\\f"))))
+
+  def getValidJSONTestArgOutput() =
+    JsArray(
+      JsObject("key" -> JsString("number"), "value" -> JsNumber(8)),
+      JsObject("key" -> JsString("bignumber"), "value" -> JsNumber(12345678912.123456789012)),
+      JsObject(
+        "key" -> JsString("objArr"),
+        "value" -> JsArray(
+          JsObject("name" -> JsString("someName"), "required" -> JsBoolean(true)),
+          JsObject("name" -> JsString("events"), "count" -> JsNumber(10)))),
+      JsObject("key" -> JsString("strArr"), "value" -> JsArray(JsString("44"), JsString("55"))),
+      JsObject("key" -> JsString("string"), "value" -> JsString("This is a string")),
+      JsObject("key" -> JsString("numArr"), "value" -> JsArray(JsNumber(44), JsNumber(55))),
+      JsObject(
+        "key" -> JsString("object"),
+        "value" -> JsObject(
+          "objString" -> JsString("aString"),
+          "objStrNum" -> JsString("123"),
+          "objNum" -> JsNumber(300),
+          "objBool" -> JsBoolean(false),
+          "objNumArr" -> JsArray(JsNumber(1), JsNumber(2)),
+          "objStrArr" -> JsArray(JsString("1"), JsString("2")))),
+      JsObject("key" -> JsString("strNum"), "value" -> JsString("9")))
+
+  def getValidJSONTestArgInput() =
+    Map(
+      "string" -> JsString("This is a string"),
+      "strNum" -> JsString("9"),
+      "number" -> JsNumber(8),
+      "bignumber" -> JsNumber(12345678912.123456789012),
+      "numArr" -> JsArray(JsNumber(44), JsNumber(55)),
+      "strArr" -> JsArray(JsString("44"), JsString("55")),
+      "objArr" -> JsArray(
+        JsObject("name" -> JsString("someName"), "required" -> JsBoolean(true)),
+        JsObject("name" -> JsString("events"), "count" -> JsNumber(10))),
+      "object" -> JsObject(
+        "objString" -> JsString("aString"),
+        "objStrNum" -> JsString("123"),
+        "objNum" -> JsNumber(300),
+        "objBool" -> JsBoolean(false),
+        "objNumArr" -> JsArray(JsNumber(1), JsNumber(2)),
+        "objStrArr" -> JsArray(JsString("1"), JsString("2"))))
+}
diff --git a/tests/src/test/scala/whisk/core/cli/test/WskActionSequenceTests.scala b/tests/src/test/scala/whisk/core/cli/test/WskActionSequenceTests.scala
index 012dc011..b46c94c5 100644
--- a/tests/src/test/scala/whisk/core/cli/test/WskActionSequenceTests.scala
+++ b/tests/src/test/scala/whisk/core/cli/test/WskActionSequenceTests.scala
@@ -22,9 +22,10 @@ import org.scalatest.junit.JUnitRunner
 
 import common.TestHelpers
 import common.TestCLIUtils
-import common.Wsk
+import common.BaseWsk
 import common.WskProps
 import common.WskTestHelpers
+import common.TestUtils.RunResult
 import spray.json._
 import whisk.core.entity.EntityPath
 
@@ -32,10 +33,10 @@ import whisk.core.entity.EntityPath
  * Tests creation and retrieval of a sequence action
  */
 @RunWith(classOf[JUnitRunner])
-class WskActionSequenceTests extends TestHelpers with WskTestHelpers {
+abstract class WskActionSequenceTests extends TestHelpers with WskTestHelpers {
 
   implicit val wskprops = WskProps()
-  val wsk = new Wsk
+  val wsk: BaseWsk
   val defaultNamespace = EntityPath.DEFAULT.asString
   val namespace = wsk.namespace.whois()
 
@@ -75,7 +76,12 @@ class WskActionSequenceTests extends TestHelpers with WskTestHelpers {
       action.create(name, Some(artifacts), kind = Some("sequence"))
     }
 
-    val stdout = wsk.action.get(name).stdout
+    val action = wsk.action.get(name)
+    verifyActionSequence(action, name, compValue, kindValue)
+  }
+
+  def verifyActionSequence(action: RunResult, name: String, compValue: JsArray, kindValue: JsString): Unit = {
+    val stdout = action.stdout
     assert(stdout.startsWith(s"ok: got action $name\n"))
     wsk.parseJsonString(stdout).fields("exec").asJsObject.fields("components") shouldBe compValue
     wsk.parseJsonString(stdout).fields("exec").asJsObject.fields("kind") shouldBe kindValue
diff --git a/tests/src/test/scala/whisk/core/cli/test/WskApiGwTests.scala b/tests/src/test/scala/whisk/core/cli/test/WskApiGwTests.scala
new file mode 100644
index 00000000..25dab826
--- /dev/null
+++ b/tests/src/test/scala/whisk/core/cli/test/WskApiGwTests.scala
@@ -0,0 +1,438 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package whisk.core.cli.test
+
+import org.junit.runner.RunWith
+import org.scalatest.junit.JUnitRunner
+
+import common.JsHelpers
+import common.StreamLogging
+import common.TestCLIUtils
+import common.TestUtils.ANY_ERROR_EXIT
+import common.TestUtils.DONTCARE_EXIT
+import common.TestUtils.SUCCESS_EXIT
+import common.Wsk
+import common.WskActorSystem
+import common.WskAdmin
+import common.WskProps
+
+/**
+ * Tests for basic CLI usage. Some of these tests require a deployed backend.
+ */
+@RunWith(classOf[JUnitRunner])
+class WskApiGwTests extends BaseApiGwTests with WskActorSystem with JsHelpers with StreamLogging {
+
+  val systemId: String = "whisk.system"
+  override implicit val wskprops = WskProps(authKey = WskAdmin.listKeys(systemId)(0)._1, namespace = systemId)
+  val wsk: common.Wsk = new Wsk
+
+  it should "reject apimgmt actions that are invoked with not enough parameters" in {
+    val invalidArgs = Seq(
+      //getApi
+      ("/whisk.system/apimgmt/getApi", ANY_ERROR_EXIT, "Invalid authentication.", Seq()),
+      //deleteApi
+      (
+        "/whisk.system/apimgmt/deleteApi",
+        ANY_ERROR_EXIT,
+        "Invalid authentication.",
+        Seq("-p", "basepath", "/ApiGwRoutemgmtActionTests_bp")),
+      (
+        "/whisk.system/apimgmt/deleteApi",
+        ANY_ERROR_EXIT,
+        "basepath is required",
+        Seq("-p", "__ow_user", "_", "-p", "accesstoken", "TOKEN")),
+      (
+        "/whisk.system/apimgmt/deleteApi",
+        ANY_ERROR_EXIT,
+        "When specifying an operation, the path is required",
+        Seq(
+          "-p",
+          "__ow_user",
+          "_",
+          "-p",
+          "accesstoken",
+          "TOKEN",
+          "-p",
+          "basepath",
+          "/ApiGwRoutemgmtActionTests_bp",
+          "-p",
+          "operation",
+          "get")),
+      //createApi
+      (
+        "/whisk.system/apimgmt/createApi",
+        ANY_ERROR_EXIT,
+        "apidoc is required",
+        Seq("-p", "__ow_user", "_", "-p", "accesstoken", "TOKEN")),
+      (
+        "/whisk.system/apimgmt/createApi",
+        ANY_ERROR_EXIT,
+        "apidoc is missing the namespace field",
+        Seq("-p", "__ow_user", "_", "-p", "accesstoken", "TOKEN", "-p", "apidoc", "{}")),
+      (
+        "/whisk.system/apimgmt/createApi",
+        ANY_ERROR_EXIT,
+        "apidoc is missing the gatewayBasePath field",
+        Seq("-p", "__ow_user", "_", "-p", "accesstoken", "TOKEN", "-p", "apidoc", """{"namespace":"_"}""")),
+      (
+        "/whisk.system/apimgmt/createApi",
+        ANY_ERROR_EXIT,
+        "apidoc is missing the gatewayPath field",
+        Seq(
+          "-p",
+          "__ow_user",
+          "_",
+          "-p",
+          "accesstoken",
+          "TOKEN",
+          "-p",
+          "apidoc",
+          """{"namespace":"_","gatewayBasePath":"/ApiGwRoutemgmtActionTests_bp"}""")),
+      (
+        "/whisk.system/apimgmt/createApi",
+        ANY_ERROR_EXIT,
+        "apidoc is missing the gatewayMethod field",
+        Seq(
+          "-p",
+          "__ow_user",
+          "_",
+          "-p",
+          "accesstoken",
+          "TOKEN",
+          "-p",
+          "apidoc",
+          """{"namespace":"_","gatewayBasePath":"/ApiGwRoutemgmtActionTests_bp","gatewayPath":"ApiGwRoutemgmtActionTests_rp"}""")),
+      (
+        "/whisk.system/apimgmt/createApi",
+        ANY_ERROR_EXIT,
+        "apidoc is missing the action field",
+        Seq(
+          "-p",
+          "__ow_user",
+          "_",
+          "-p",
+          "accesstoken",
+          "TOKEN",
+          "-p",
+          "apidoc",
+          """{"namespace":"_","gatewayBasePath":"/ApiGwRoutemgmtActionTests_bp","gatewayPath":"ApiGwRoutemgmtActionTests_rp","gatewayMethod":"get"}""")),
+      (
+        "/whisk.system/apimgmt/createApi",
+        ANY_ERROR_EXIT,
+        "action is missing the backendMethod field",
+        Seq(
+          "-p",
+          "__ow_user",
+          "_",
+          "-p",
+          "accesstoken",
+          "TOKEN",
+          "-p",
+          "apidoc",
+          """{"namespace":"_","gatewayBasePath":"/ApiGwRoutemgmtActionTests_bp","gatewayPath":"ApiGwRoutemgmtActionTests_rp","gatewayMethod":"get","action":{}}""")),
+      (
+        "/whisk.system/apimgmt/createApi",
+        ANY_ERROR_EXIT,
+        "action is missing the backendUrl field",
+        Seq(
+          "-p",
+          "__ow_user",
+          "_",
+          "-p",
+          "accesstoken",
+          "TOKEN",
+          "-p",
+          "apidoc",
+          """{"namespace":"_","gatewayBasePath":"/ApiGwRoutemgmtActionTests_bp","gatewayPath":"ApiGwRoutemgmtActionTests_rp","gatewayMethod":"get","action":{"backendMethod":"post"}}""")),
+      (
+        "/whisk.system/apimgmt/createApi",
+        ANY_ERROR_EXIT,
+        "action is missing the namespace field",
+        Seq(
+          "-p",
+          "__ow_user",
+          "_",
+          "-p",
+          "accesstoken",
+          "TOKEN",
+          "-p",
+          "apidoc",
+          """{"namespace":"_","gatewayBasePath":"/ApiGwRoutemgmtActionTests_bp","gatewayPath":"ApiGwRoutemgmtActionTests_rp","gatewayMethod":"get","action":{"backendMethod":"post","backendUrl":"URL"}}""")),
+      (
+        "/whisk.system/apimgmt/createApi",
+        ANY_ERROR_EXIT,
+        "action is missing the name field",
+        Seq(
+          "-p",
+          "__ow_user",
+          "_",
+          "-p",
+          "accesstoken",
+          "TOKEN",
+          "-p",
+          "apidoc",
+          """{"namespace":"_","gatewayBasePath":"/ApiGwRoutemgmtActionTests_bp","gatewayPath":"ApiGwRoutemgmtActionTests_rp","gatewayMethod":"get","action":{"backendMethod":"post","backendUrl":"URL","namespace":"_"}}""")),
+      (
+        "/whisk.system/apimgmt/createApi",
+        ANY_ERROR_EXIT,
+        "action is missing the authkey field",
+        Seq(
+          "-p",
+          "__ow_user",
+          "_",
+          "-p",
+          "accesstoken",
+          "TOKEN",
+          "-p",
+          "apidoc",
+          """{"namespace":"_","gatewayBasePath":"/ApiGwRoutemgmtActionTests_bp","gatewayPath":"ApiGwRoutemgmtActionTests_rp","gatewayMethod":"get","action":{"backendMethod":"post","backendUrl":"URL","namespace":"_","name":"N"}}""")),
+      (
+        "/whisk.system/apimgmt/createApi",
+        ANY_ERROR_EXIT,
+        "swagger and gatewayBasePath are mutually exclusive and cannot be specified together",
+        Seq(
+          "-p",
+          "__ow_user",
+          "_",
+          "-p",
+          "accesstoken",
+          "TOKEN",
+          "-p",
+          "apidoc",
+          """{"namespace":"_","gatewayBasePath":"/ApiGwRoutemgmtActionTests_bp","gatewayPath":"ApiGwRoutemgmtActionTests_rp","gatewayMethod":"get","action":{"backendMethod":"post","backendUrl":"URL","namespace":"_","name":"N","authkey":"XXXX"},"swagger":{}}""")),
+      (
+        "/whisk.system/apimgmt/createApi",
+        ANY_ERROR_EXIT,
+        "apidoc field cannot be parsed. Ensure it is valid JSON",
+        Seq("-p", "__ow_user", "_", "-p", "accesstoken", "TOKEN", "-p", "apidoc", "{1:[}}}")))
+
+    invalidArgs foreach {
+      case (action: String, exitcode: Int, errmsg: String, params: Seq[String]) =>
+        val cmd: Seq[String] = Seq(
+          "action",
+          "invoke",
+          action,
+          "-i",
+          "-b",
+          "-r",
+          "--apihost",
+          wskprops.apihost,
+          "--auth",
+          wskprops.authKey) ++ params
+        val rr = wsk.cli(cmd, expectedExitCode = exitcode)
+        rr.stderr should include regex (errmsg)
+    }
+  }
+
+  it should "reject an API created with a non-existent action" in {
+    val testName = "CLI_APIGWTEST15"
+    val testbasepath = "/" + testName + "_bp"
+    val testrelpath = "/path"
+    val testnewrelpath = "/path_new"
+    val testurlop = "get"
+    val testapiname = testName + " API Name"
+    val actionName = testName + "_action"
+    try {
+      val rr = apiCreate(
+        basepath = Some(testbasepath),
+        relpath = Some(testrelpath),
+        operation = Some(testurlop),
+        action = Some(actionName),
+        apiname = Some(testapiname),
+        expectedExitCode = ANY_ERROR_EXIT)
+      rr.stderr should include("does not exist")
+    } finally {
+      apiDelete(basepathOrApiName = testbasepath, expectedExitCode = DONTCARE_EXIT)
+    }
+  }
+
+  it should "reject an API created with an action that is not a web action" in {
+    val testName = "CLI_APIGWTEST16"
+    val testbasepath = "/" + testName + "_bp"
+    val testrelpath = "/path"
+    val testnewrelpath = "/path_new"
+    val testurlop = "get"
+    val testapiname = testName + " API Name"
+    val actionName = testName + "_action"
+    try {
+      // Create the action for the API.  It must NOT be a "web-action" action for this test
+      val file = TestCLIUtils.getTestActionFilename(s"echo.js")
+      wsk.action.create(name = actionName, artifact = Some(file), expectedExitCode = SUCCESS_EXIT)
+
+      val rr = apiCreate(
+        basepath = Some(testbasepath),
+        relpath = Some(testrelpath),
+        operation = Some(testurlop),
+        action = Some(actionName),
+        apiname = Some(testapiname),
+        expectedExitCode = ANY_ERROR_EXIT)
+      rr.stderr should include("is not a web action")
+    } finally {
+      wsk.action.delete(name = actionName, expectedExitCode = DONTCARE_EXIT)
+      apiDelete(basepathOrApiName = testbasepath, expectedExitCode = DONTCARE_EXIT)
+    }
+  }
+
+  it should "reject API export when export type is invalid" in {
+    val testName = "CLI_APIGWTEST18"
+    val testbasepath = "/" + testName + "_bp"
+
+    val rr = apiGet(basepathOrApiName = Some(testbasepath), format = Some("BadType"), expectedExitCode = ANY_ERROR_EXIT)
+    rr.stderr should include("Invalid format type")
+  }
+
+  it should "list api alphabetically by Base/Rel/Verb" in {
+    val baseName = "/BaseTestPathApiList"
+    val actionName = "actionName"
+    val file = TestCLIUtils.getTestActionFilename(s"echo-web-http.js")
+    try {
+      // Create Action for apis
+      var action =
+        wsk.action.create(name = actionName, artifact = Some(file), expectedExitCode = SUCCESS_EXIT, web = Some("true"))
+      println("action creation: " + action.stdout)
+      // Create apis
+      for (i <- 1 to 3) {
+        val base = s"$baseName$i"
+        var api = apiCreate(
+          basepath = Some(base),
+          relpath = Some("/relPath"),
+          operation = Some("GET"),
+          action = Some(actionName))
+        println("api creation: " + api.stdout)
+      }
+      val original = apiList(nameSort = Some(true))
+      val originalFull = apiList(full = Some(true), nameSort = Some(true))
+      val scalaSorted = List(s"${baseName}1" + "/", s"${baseName}2" + "/", s"${baseName}3" + "/")
+
+      val regex = s"${baseName}[1-3]/".r
+      val list = (regex.findAllMatchIn(original.stdout)).toList
+      val listFull = (regex.findAllMatchIn(originalFull.stdout)).toList
+
+      scalaSorted.toString shouldEqual list.toString
+      scalaSorted.toString shouldEqual listFull.toString
+
+    } finally {
+      // Clean up Apis
+      for (i <- 1 to 3) {
+        apiDelete(basepathOrApiName = s"${baseName}$i", expectedExitCode = DONTCARE_EXIT)
+      }
+      wsk.action.delete(name = actionName, expectedExitCode = DONTCARE_EXIT)
+    }
+  }
+
+  it should "successfully export an API in YAML format" in {
+    val testName = "CLI_APIGWTEST19"
+    val testbasepath = "/" + testName + "_bp"
+    val testrelpath = "/path"
+    val testnewrelpath = "/path_new"
+    val testurlop = "get"
+    val testapiname = testName + " API Name"
+    val actionName = testName + "_action"
+    val responseType = "http"
+    try {
+      // Create the action for the API.  It must be a "web-action" action.
+      val file = TestCLIUtils.getTestActionFilename(s"echo.js")
+      wsk.action.create(name = actionName, artifact = Some(file), expectedExitCode = SUCCESS_EXIT, web = Some("true"))
+
+      var rr = apiCreate(
+        basepath = Some(testbasepath),
+        relpath = Some(testrelpath),
+        operation = Some(testurlop),
+        action = Some(actionName),
+        apiname = Some(testapiname),
+        responsetype = Some(responseType))
+      rr.stdout should include("ok: created API")
+
+      rr = apiGet(basepathOrApiName = Some(testapiname), format = Some("yaml"))
+      rr.stdout should include(s"basePath: ${testbasepath}")
+    } finally {
+      wsk.action.delete(name = actionName, expectedExitCode = DONTCARE_EXIT)
+      apiDelete(basepathOrApiName = testbasepath, expectedExitCode = DONTCARE_EXIT)
+    }
+  }
+
+  it should "successfully export an API when JSON format is explcitly specified" in {
+    val testName = "CLI_APIGWTEST20"
+    val testbasepath = "/" + testName + "_bp"
+    val testrelpath = "/path"
+    val testnewrelpath = "/path_new"
+    val testurlop = "get"
+    val testapiname = testName + " API Name"
+    val actionName = testName + "_action"
+    val responseType = "http"
+    try {
+      // Create the action for the API.  It must be a "web-action" action.
+      val file = TestCLIUtils.getTestActionFilename(s"echo.js")
+      wsk.action.create(name = actionName, artifact = Some(file), expectedExitCode = SUCCESS_EXIT, web = Some("true"))
+
+      var rr = apiCreate(
+        basepath = Some(testbasepath),
+        relpath = Some(testrelpath),
+        operation = Some(testurlop),
+        action = Some(actionName),
+        apiname = Some(testapiname),
+        responsetype = Some(responseType))
+      rr.stdout should include("ok: created API")
+
+      rr = apiGet(basepathOrApiName = Some(testapiname), format = Some("json"))
+      rr.stdout should include(testbasepath)
+      rr.stdout should include(s"${actionName}")
+      rr.stdout should include regex (""""cors":\s*\{\s*\n\s*"enabled":\s*true""")
+      rr.stdout should include regex (s""""target-url":\\s+.*${actionName}.${responseType}""")
+    } finally {
+      wsk.action.delete(name = actionName, expectedExitCode = DONTCARE_EXIT)
+      apiDelete(basepathOrApiName = testbasepath, expectedExitCode = DONTCARE_EXIT)
+    }
+  }
+
+  it should "successfully create an API from a YAML formatted API configuration file" in {
+    val testName = "CLI_APIGWTEST21"
+    val testbasepath = "/bp"
+    val testrelpath = "/rp"
+    val testurlop = "get"
+    val testapiname = testbasepath
+    val actionName = "webhttpecho"
+    val swaggerPath = TestCLIUtils.getTestApiGwFilename(s"local.api.yaml")
+    try {
+      var rr = apiCreate(swagger = Some(swaggerPath))
+      println("api create stdout: " + rr.stdout)
+      println("api create stderror: " + rr.stderr)
+      rr.stdout should include("ok: created API")
+      rr = apiList(basepathOrApiName = Some(testbasepath))
+      rr.stdout should include("ok: APIs")
+      rr.stdout should include regex (s"/[@\\w._\\-]+/${actionName}\\s+${testurlop}\\s+${testapiname}\\s+")
+      rr.stdout should include(testbasepath + testrelpath)
+    } finally {
+      apiDelete(basepathOrApiName = testbasepath, expectedExitCode = DONTCARE_EXIT)
+    }
+  }
+
+  it should "reject creation of an API from invalid YAML formatted API configuration file" in {
+    val testName = "CLI_APIGWTEST22"
+    val testbasepath = "/" + testName + "_bp"
+    val swaggerPath = TestCLIUtils.getTestApiGwFilename(s"local.api.bad.yaml")
+    try {
+      val rr = apiCreate(swagger = Some(swaggerPath), expectedExitCode = ANY_ERROR_EXIT)
+      println("api create stdout: " + rr.stdout)
+      println("api create stderror: " + rr.stderr)
+      rr.stderr should include("Unable to parse YAML configuration file")
+    } finally {
+      apiDelete(basepathOrApiName = testbasepath, expectedExitCode = DONTCARE_EXIT)
+    }
+  }
+}
diff --git a/tests/src/test/scala/whisk/core/cli/test/WskBasicUsageTests.scala b/tests/src/test/scala/whisk/core/cli/test/WskBasicUsageTests.scala
index abac6c20..8c60c905 100644
--- a/tests/src/test/scala/whisk/core/cli/test/WskBasicUsageTests.scala
+++ b/tests/src/test/scala/whisk/core/cli/test/WskBasicUsageTests.scala
@@ -27,13 +27,10 @@ import scala.language.postfixOps
 import scala.concurrent.duration.Duration
 import scala.concurrent.duration.DurationInt
 import scala.util.Random
-
 import org.junit.runner.RunWith
 import org.scalatest.junit.JUnitRunner
-
 import common.TestHelpers
 import common.TestCLIUtils
-import common.TestUtils
 import common.TestUtils._
 import common.WhiskProperties
 import common.Wsk
@@ -445,7 +442,7 @@ class WskBasicUsageTests extends TestHelpers with WskTestHelpers {
       activation.logs.get.mkString(" ") should include("action list has this many actions")
     }
 
-    wsk.action.delete(name, expectedExitCode = TestUtils.NOT_FOUND)
+    wsk.action.delete(name, expectedExitCode = NOT_FOUND)
   }
 
   it should "invoke an action receiving context properties" in withAssetCleaner(wskprops) { (wp, assetHelper) =>
@@ -1126,6 +1123,24 @@ class WskBasicUsageTests extends TestHelpers with WskTestHelpers {
     }
   }
 
+  it should "invoke a feed action with the correct lifecyle event when creating, retrieving and deleting a feed trigger" in withAssetCleaner(
+    wskprops) { (wp, assetHelper) =>
+    val actionName = "echo"
+    val triggerName = "feedTest"
+
+    assetHelper.withCleaner(wsk.action, actionName) { (action, _) =>
+      action.create(actionName, Some(TestCLIUtils.getTestActionFilename("echo.js")))
+    }
+
+    try {
+      wsk.trigger.create(triggerName, feed = Some(actionName)).stdout should include(""""lifecycleEvent": "CREATE"""")
+
+      wsk.trigger.get(triggerName).stdout should include(""""lifecycleEvent": "READ"""")
+    } finally {
+      wsk.trigger.delete(triggerName).stdout should include(""""lifecycleEvent": "DELETE"""")
+    }
+  }
+
   it should "denote bound trigger parameters for trigger summaries" in withAssetCleaner(wskprops) { (wp, assetHelper) =>
     val trgBoundParams = "trgBoundParams"
     val trgParamAnnot = "trgParamAnnot"
diff --git a/tests/src/test/scala/whisk/core/cli/test/WskCliActionSequenceTests.scala b/tests/src/test/scala/whisk/core/cli/test/WskCliActionSequenceTests.scala
new file mode 100644
index 00000000..06da3489
--- /dev/null
+++ b/tests/src/test/scala/whisk/core/cli/test/WskCliActionSequenceTests.scala
@@ -0,0 +1,28 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package whisk.core.cli.test
+
+import org.junit.runner.RunWith
+import org.scalatest.junit.JUnitRunner
+
+import common.Wsk
+
+@RunWith(classOf[JUnitRunner])
+class WskCliActionSequenceTests extends WskActionSequenceTests {
+  override lazy val wsk = new Wsk
+}
diff --git a/tests/src/test/scala/whisk/core/cli/test/WskCliEntitlementTests.scala b/tests/src/test/scala/whisk/core/cli/test/WskCliEntitlementTests.scala
new file mode 100644
index 00000000..e25369cb
--- /dev/null
+++ b/tests/src/test/scala/whisk/core/cli/test/WskCliEntitlementTests.scala
@@ -0,0 +1,34 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package whisk.core.cli.test
+
+import org.junit.runner.RunWith
+import org.scalatest.junit.JUnitRunner
+
+import common.Wsk
+import common.TestUtils.FORBIDDEN
+import common.TestUtils.TIMEOUT
+import common.TestUtils.NOT_FOUND
+
+@RunWith(classOf[JUnitRunner])
+class WskCliEntitlementTests extends WskEntitlementTests {
+  override lazy val wsk = new Wsk
+  override lazy val forbiddenCode = FORBIDDEN
+  override lazy val timeoutCode = TIMEOUT
+  override lazy val notFoundCode = NOT_FOUND
+}
diff --git a/tests/src/test/scala/whisk/core/cli/test/WskCliWebActionsTests.scala b/tests/src/test/scala/whisk/core/cli/test/WskCliWebActionsTests.scala
new file mode 100644
index 00000000..048fdced
--- /dev/null
+++ b/tests/src/test/scala/whisk/core/cli/test/WskCliWebActionsTests.scala
@@ -0,0 +1,28 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package whisk.core.cli.test
+
+import org.junit.runner.RunWith
+import org.scalatest.junit.JUnitRunner
+
+import common.Wsk
+
+@RunWith(classOf[JUnitRunner])
+class WskCliWebActionsTests extends WskWebActionsTests {
+  override lazy val wsk: common.Wsk = new Wsk
+}
diff --git a/tests/src/test/scala/whisk/core/cli/test/WskConfigTests.scala b/tests/src/test/scala/whisk/core/cli/test/WskConfigTests.scala
index 795a26a9..220953ba 100644
--- a/tests/src/test/scala/whisk/core/cli/test/WskConfigTests.scala
+++ b/tests/src/test/scala/whisk/core/cli/test/WskConfigTests.scala
@@ -132,6 +132,20 @@ class WskConfigTests extends TestHelpers with WskTestHelpers {
     }
   }
 
+  it should "get apihost removing any trailing white spaces and line comments" in {
+    val tmpwskprops = File.createTempFile("wskprops", ".tmp")
+    try {
+      val writer = new BufferedWriter(new FileWriter(tmpwskprops))
+      writer.write(s"APIHOST=http://localhost:10001    # This is a comment!   ")
+      writer.close()
+      val env = Map("WSK_CONFIG_FILE" -> tmpwskprops.getAbsolutePath())
+      val stdout = wsk.cli(Seq("property", "get", "-i", "--apihost"), env = env).stdout
+      stdout should include regex ("whisk API host\\s+http://localhost:10001$")
+    } finally {
+      tmpwskprops.delete()
+    }
+  }
+
   it should "set apihost, auth, and namespace" in {
     val tmpwskprops = File.createTempFile("wskprops", ".tmp")
     try {
@@ -265,6 +279,72 @@ class WskConfigTests extends TestHelpers with WskTestHelpers {
     }
   }
 
+  it should "return configure the missing Cert file" in {
+    val tmpwskprops = File.createTempFile("wskprops", ".tmp")
+    val keyFile = File.createTempFile("key", ".pem")
+    try {
+      val writer = new BufferedWriter(new FileWriter(tmpwskprops))
+      writer.write(s"KEY=${keyFile.getAbsolutePath()}\n")
+      writer.close()
+      val env = Map("WSK_CONFIG_FILE" -> tmpwskprops.getAbsolutePath())
+      val stderr = wsk
+        .cli(
+          Seq("property", "get", "--apibuild", "--apihost", wskprops.apihost, "--apiversion", wskprops.apiversion),
+          env = env,
+          expectedExitCode = ERROR_EXIT)
+        .stderr
+      stderr should include regex ("""The Cert file is not configured. Please configure the missing Cert file.""")
+    } finally {
+      tmpwskprops.delete()
+      keyFile.delete()
+    }
+  }
+
+  it should "return configure the missing Key file" in {
+    val tmpwskprops = File.createTempFile("wskprops", ".tmp")
+    val certFile = File.createTempFile("cert", ".pem")
+    try {
+      val writer = new BufferedWriter(new FileWriter(tmpwskprops))
+      writer.write(s"CERT=${certFile.getAbsolutePath()}\n")
+      writer.close()
+      val env = Map("WSK_CONFIG_FILE" -> tmpwskprops.getAbsolutePath())
+      val stderr = wsk
+        .cli(
+          Seq("property", "get", "--apibuild", "--apihost", wskprops.apihost, "--apiversion", wskprops.apiversion),
+          env = env,
+          expectedExitCode = ERROR_EXIT)
+        .stderr
+      stderr should include regex ("""The Key file is not configured. Please configure the missing Key file.""")
+    } finally {
+      tmpwskprops.delete()
+      certFile.delete()
+    }
+  }
+
+  it should "return unable to load the X509 key pair with both Cert and Key files missing" in {
+    val tmpwskprops = File.createTempFile("wskprops", ".tmp")
+    val certFile = File.createTempFile("cert", ".pem")
+    val keyFile = File.createTempFile("key", ".pem")
+    try {
+      val writer = new BufferedWriter(new FileWriter(tmpwskprops))
+      writer.write(s"CERT=${certFile.getAbsolutePath()}\n")
+      writer.write(s"KEY=${keyFile.getAbsolutePath()}\n")
+      writer.close()
+      val env = Map("WSK_CONFIG_FILE" -> tmpwskprops.getAbsolutePath())
+      val stderr = wsk
+        .cli(
+          Seq("property", "get", "--apibuild", "--apihost", wskprops.apihost, "--apiversion", wskprops.apiversion),
+          env = env,
+          expectedExitCode = ERROR_EXIT)
+        .stderr
+      stderr should include regex ("""Unable to load the X509 key pair due to the following reason""")
+    } finally {
+      tmpwskprops.delete()
+      certFile.delete()
+      keyFile.delete()
+    }
+  }
+
   it should "set api host with or without http prefix" in {
     val tmpwskprops = File.createTempFile("wskprops", ".tmp")
     try {
@@ -343,4 +423,70 @@ class WskConfigTests extends TestHelpers with WskTestHelpers {
     }
     tmpProps.delete()
   }
+
+  it should "return configure the missing Cert file for action" in {
+    val tmpwskprops = File.createTempFile("wskprops", ".tmp")
+    val keyFile = File.createTempFile("key", ".pem")
+    try {
+      val writer = new BufferedWriter(new FileWriter(tmpwskprops))
+      writer.write(s"KEY=${keyFile.getAbsolutePath()}\n")
+      writer.close()
+      val env = Map("WSK_CONFIG_FILE" -> tmpwskprops.getAbsolutePath())
+      val stderr = wsk
+        .cli(
+          Seq("action", "list", "--apihost", wskprops.apihost, "--apiversion", wskprops.apiversion),
+          env = env,
+          expectedExitCode = ERROR_EXIT)
+        .stderr
+      stderr should include regex ("""The Cert file is not configured. Please configure the missing Cert file.""")
+    } finally {
+      tmpwskprops.delete()
+      keyFile.delete()
+    }
+  }
+
+  it should "return configure the missing Key file for action" in {
+    val tmpwskprops = File.createTempFile("wskprops", ".tmp")
+    val certFile = File.createTempFile("cert", ".pem")
+    try {
+      val writer = new BufferedWriter(new FileWriter(tmpwskprops))
+      writer.write(s"CERT=${certFile.getAbsolutePath()}\n")
+      writer.close()
+      val env = Map("WSK_CONFIG_FILE" -> tmpwskprops.getAbsolutePath())
+      val stderr = wsk
+        .cli(
+          Seq("action", "list", "--apihost", wskprops.apihost, "--apiversion", wskprops.apiversion),
+          env = env,
+          expectedExitCode = ERROR_EXIT)
+        .stderr
+      stderr should include regex ("""The Key file is not configured. Please configure the missing Key file.""")
+    } finally {
+      tmpwskprops.delete()
+      certFile.delete()
+    }
+  }
+
+  it should "return unable to load the X509 key pair with both Cert and Key files missing for action" in {
+    val tmpwskprops = File.createTempFile("wskprops", ".tmp")
+    val certFile = File.createTempFile("cert", ".pem")
+    val keyFile = File.createTempFile("key", ".pem")
+    try {
+      val writer = new BufferedWriter(new FileWriter(tmpwskprops))
+      writer.write(s"CERT=${certFile.getAbsolutePath()}\n")
+      writer.write(s"KEY=${keyFile.getAbsolutePath()}\n")
+      writer.close()
+      val env = Map("WSK_CONFIG_FILE" -> tmpwskprops.getAbsolutePath())
+      val stderr = wsk
+        .cli(
+          Seq("action", "list", "--apihost", wskprops.apihost, "--apiversion", wskprops.apiversion),
+          env = env,
+          expectedExitCode = ERROR_EXIT)
+        .stderr
+      stderr should include regex ("""Unable to load the X509 key pair due to the following reason""")
+    } finally {
+      tmpwskprops.delete()
+      certFile.delete()
+      keyFile.delete()
+    }
+  }
 }
diff --git a/tests/src/test/scala/whisk/core/cli/test/WskEntitlementTests.scala b/tests/src/test/scala/whisk/core/cli/test/WskEntitlementTests.scala
index 9a96e4da..926a4da3 100644
--- a/tests/src/test/scala/whisk/core/cli/test/WskEntitlementTests.scala
+++ b/tests/src/test/scala/whisk/core/cli/test/WskEntitlementTests.scala
@@ -23,10 +23,8 @@ import org.scalatest.junit.JUnitRunner
 
 import common.TestHelpers
 import common.TestCLIUtils
-import common.TestUtils.FORBIDDEN
-import common.TestUtils.NOT_FOUND
-import common.TestUtils.TIMEOUT
-import common.Wsk
+import common.TestUtils.RunResult
+import common.BaseWsk
 import common.WskProps
 import common.WskTestHelpers
 import spray.json._
@@ -35,11 +33,14 @@ import whisk.core.entity.Subject
 import whisk.core.entity.WhiskPackage
 
 @RunWith(classOf[JUnitRunner])
-class WskEntitlementTests extends TestHelpers with WskTestHelpers with BeforeAndAfterAll {
+abstract class WskEntitlementTests extends TestHelpers with WskTestHelpers with BeforeAndAfterAll {
 
-  val wsk = new Wsk
+  val wsk: BaseWsk
   lazy val defaultWskProps = WskProps()
   lazy val guestWskProps = getAdditionalTestSubject(Subject().asString)
+  val forbiddenCode: Int
+  val timeoutCode: Int
+  val notFoundCode: Int
 
   override def afterAll() = {
     disposeAdditionalTestSubject(guestWskProps.namespace)
@@ -61,12 +62,13 @@ class WskEntitlementTests extends TestHelpers with WskTestHelpers with BeforeAnd
       }
 
       val fullyQualifiedActionName = s"/$guestNamespace/$privateAction"
-      wsk.action.get(fullyQualifiedActionName, expectedExitCode = FORBIDDEN)(defaultWskProps).stderr should include(
-        "not authorized")
+      wsk.action
+        .get(fullyQualifiedActionName, expectedExitCode = forbiddenCode)(defaultWskProps)
+        .stderr should include("not authorized")
 
       withAssetCleaner(defaultWskProps) { (wp, assetHelper) =>
         assetHelper.withCleaner(wsk.action, fullyQualifiedActionName, confirmDelete = false) { (action, name) =>
-          val rr = action.create(name, None, update = true, expectedExitCode = FORBIDDEN)(wp)
+          val rr = action.create(name, None, update = true, expectedExitCode = forbiddenCode)(wp)
           rr.stderr should include("not authorized")
           rr
         }
@@ -77,17 +79,19 @@ class WskEntitlementTests extends TestHelpers with WskTestHelpers with BeforeAnd
             Some(fullyQualifiedActionName),
             kind = Some("sequence"),
             update = true,
-            expectedExitCode = FORBIDDEN)(wp)
+            expectedExitCode = forbiddenCode)(wp)
           rr.stderr should include("not authorized")
           rr
         }
       }
 
-      wsk.action.delete(fullyQualifiedActionName, expectedExitCode = FORBIDDEN)(defaultWskProps).stderr should include(
-        "not authorized")
+      wsk.action
+        .delete(fullyQualifiedActionName, expectedExitCode = forbiddenCode)(defaultWskProps)
+        .stderr should include("not authorized")
 
-      wsk.action.invoke(fullyQualifiedActionName, expectedExitCode = FORBIDDEN)(defaultWskProps).stderr should include(
-        "not authorized")
+      wsk.action
+        .invoke(fullyQualifiedActionName, expectedExitCode = forbiddenCode)(defaultWskProps)
+        .stderr should include("not authorized")
   }
 
   it should "reject deleting action in shared package not owned by authkey" in withAssetCleaner(guestWskProps) {
@@ -104,7 +108,7 @@ class WskEntitlementTests extends TestHelpers with WskTestHelpers with BeforeAnd
 
       val fullyQualifiedActionName = s"/$guestNamespace/$fullSampleActionName"
       wsk.action.get(fullyQualifiedActionName)(defaultWskProps)
-      wsk.action.delete(fullyQualifiedActionName, expectedExitCode = FORBIDDEN)(defaultWskProps)
+      wsk.action.delete(fullyQualifiedActionName, expectedExitCode = forbiddenCode)(defaultWskProps)
   }
 
   it should "reject create action in shared package not owned by authkey" in withAssetCleaner(guestWskProps) {
@@ -118,7 +122,7 @@ class WskEntitlementTests extends TestHelpers with WskTestHelpers with BeforeAnd
 
       withAssetCleaner(defaultWskProps) { (wp, assetHelper) =>
         assetHelper.withCleaner(wsk.action, fullyQualifiedActionName, confirmDelete = false) { (action, name) =>
-          action.create(name, file, expectedExitCode = FORBIDDEN)(wp)
+          action.create(name, file, expectedExitCode = forbiddenCode)(wp)
         }
       }
   }
@@ -136,7 +140,8 @@ class WskEntitlementTests extends TestHelpers with WskTestHelpers with BeforeAnd
       }
 
       val fullyQualifiedActionName = s"/$guestNamespace/$fullSampleActionName"
-      wsk.action.create(fullyQualifiedActionName, None, update = true, expectedExitCode = FORBIDDEN)(defaultWskProps)
+      wsk.action.create(fullyQualifiedActionName, None, update = true, expectedExitCode = forbiddenCode)(
+        defaultWskProps)
   }
 
   behavior of "Wsk Package Listing"
@@ -146,9 +151,13 @@ class WskEntitlementTests extends TestHelpers with WskTestHelpers with BeforeAnd
       pkg.create(samplePackage, shared = Some(true))(wp)
     }
 
-    val fullyQualifiedPackageName = s"/$guestNamespace/$samplePackage"
-    val result = wsk.pkg.list(Some(s"/$guestNamespace"))(defaultWskProps).stdout
-    result should include regex (fullyQualifiedPackageName + """\s+shared""")
+    val packageList = wsk.pkg.list(Some(s"/$guestNamespace"))(defaultWskProps)
+    verifyPackageSharedList(packageList, guestNamespace, samplePackage)
+  }
+
+  def verifyPackageSharedList(packageList: RunResult, namespace: String, packageName: String): Unit = {
+    val fullyQualifiedPackageName = s"/$namespace/$packageName"
+    packageList.stdout should include regex (fullyQualifiedPackageName + """\s+shared""")
   }
 
   it should "not list private packages" in withAssetCleaner(guestWskProps) { (wp, assetHelper) =>
@@ -156,9 +165,13 @@ class WskEntitlementTests extends TestHelpers with WskTestHelpers with BeforeAnd
       pkg.create(samplePackage)(wp)
     }
 
-    val fullyQualifiedPackageName = s"/$guestNamespace/$samplePackage"
-    val result = wsk.pkg.list(Some(s"/$guestNamespace"))(defaultWskProps).stdout
-    result should not include regex(fullyQualifiedPackageName)
+    val packageList = wsk.pkg.list(Some(s"/$guestNamespace"))(defaultWskProps)
+    verifyPackageNotSharedList(packageList, guestNamespace, samplePackage)
+  }
+
+  def verifyPackageNotSharedList(packageList: RunResult, namespace: String, packageName: String): Unit = {
+    val fullyQualifiedPackageName = s"/$namespace/$packageName"
+    packageList.stdout should not include regex(fullyQualifiedPackageName)
   }
 
   it should "list shared package actions" in withAssetCleaner(guestWskProps) { (wp, assetHelper) =>
@@ -173,9 +186,13 @@ class WskEntitlementTests extends TestHelpers with WskTestHelpers with BeforeAnd
     }
 
     val fullyQualifiedPackageName = s"/$guestNamespace/$samplePackage"
-    val fullyQualifiedActionName = s"/$guestNamespace/$fullSampleActionName"
-    val result = wsk.action.list(Some(fullyQualifiedPackageName))(defaultWskProps).stdout
-    result should include regex (fullyQualifiedActionName)
+    val packageList = wsk.action.list(Some(fullyQualifiedPackageName))(defaultWskProps)
+    verifyPackageList(packageList, guestNamespace, samplePackage, sampleAction)
+  }
+
+  def verifyPackageList(packageList: RunResult, namespace: String, packageName: String, actionName: String): Unit = {
+    val result = packageList.stdout
+    result should include regex (s"/$namespace/$packageName/$actionName")
   }
 
   behavior of "Wsk Package Binding"
@@ -212,7 +229,7 @@ class WskEntitlementTests extends TestHelpers with WskTestHelpers with BeforeAnd
     val provider = s"/$guestNamespace/$samplePackage"
     withAssetCleaner(defaultWskProps) { (wp, assetHelper) =>
       assetHelper.withCleaner(wsk.pkg, name, confirmDelete = false) { (pkg, _) =>
-        pkg.bind(provider, name, expectedExitCode = FORBIDDEN)(wp)
+        pkg.bind(provider, name, expectedExitCode = forbiddenCode)(wp)
       }
     }
   }
@@ -231,12 +248,8 @@ class WskEntitlementTests extends TestHelpers with WskTestHelpers with BeforeAnd
     }
 
     val fullyQualifiedActionName = s"/$guestNamespace/$fullSampleActionName"
-    val stdout = wsk.action.get(fullyQualifiedActionName)(defaultWskProps).stdout
-    stdout should include("name")
-    stdout should include("parameters")
-    stdout should include("limits")
-    stdout should include regex (""""key": "a"""")
-    stdout should include regex (""""value": "A"""")
+    val action = wsk.action.get(fullyQualifiedActionName)(defaultWskProps)
+    verifyAction(action)
 
     val run = wsk.action.invoke(fullyQualifiedActionName)(defaultWskProps)
 
@@ -245,6 +258,15 @@ class WskEntitlementTests extends TestHelpers with WskTestHelpers with BeforeAnd
     })(defaultWskProps)
   }
 
+  def verifyAction(action: RunResult) = {
+    val stdout = action.stdout
+    stdout should include("name")
+    stdout should include("parameters")
+    stdout should include("limits")
+    stdout should include regex (""""key": "a"""")
+    stdout should include regex (""""value": "A"""")
+  }
+
   it should "invoke an action sequence from package" in withAssetCleaner(guestWskProps) { (wp, assetHelper) =>
     assetHelper.withCleaner(wsk.pkg, samplePackage) { (pkg, _) =>
       pkg.create(samplePackage, parameters = Map("a" -> "A".toJson), shared = Some(true))(wp)
@@ -294,7 +316,7 @@ class WskEntitlementTests extends TestHelpers with WskTestHelpers with BeforeAnd
 
       // change package visibility
       wsk.pkg.create(privateSamplePackage, update = true, shared = Some(false))(guestwp)
-      wsk.action.invoke("sequence", expectedExitCode = FORBIDDEN)(defaultWskProps)
+      wsk.action.invoke("sequence", expectedExitCode = forbiddenCode)(defaultWskProps)
     }
   }
 
@@ -342,9 +364,9 @@ class WskEntitlementTests extends TestHelpers with WskTestHelpers with BeforeAnd
       val fullyQualifiedFeedName = s"/$guestNamespace/$sampleFeed"
       withAssetCleaner(defaultWskProps) { (wp, assetHelper) =>
         assetHelper.withCleaner(wsk.trigger, "badfeed", confirmDelete = false) { (trigger, name) =>
-          trigger.create(name, feed = Some(fullyQualifiedFeedName), expectedExitCode = TIMEOUT)(wp)
+          trigger.create(name, feed = Some(fullyQualifiedFeedName), expectedExitCode = timeoutCode)(wp)
         }
-        wsk.trigger.get("badfeed", expectedExitCode = NOT_FOUND)(wp)
+        wsk.trigger.get("badfeed", expectedExitCode = notFoundCode)(wp)
       }
   }
 
diff --git a/tests/src/test/scala/whisk/core/cli/test/WskWebActionsTests.scala b/tests/src/test/scala/whisk/core/cli/test/WskWebActionsTests.scala
index 017d4b11..1a5976a0 100644
--- a/tests/src/test/scala/whisk/core/cli/test/WskWebActionsTests.scala
+++ b/tests/src/test/scala/whisk/core/cli/test/WskWebActionsTests.scala
@@ -33,7 +33,7 @@ import com.jayway.restassured.response.Header
 import common.TestHelpers
 import common.TestCLIUtils
 import common.WhiskProperties
-import common.Wsk
+import common.BaseWsk
 import common.WskProps
 import common.WskTestHelpers
 import spray.json._
@@ -48,10 +48,10 @@ import whisk.core.entity.Subject
   * Tests web actions.
   */
 @RunWith(classOf[JUnitRunner])
-class WskWebActionsTests extends TestHelpers with WskTestHelpers with RestUtil with BeforeAndAfterAll {
+abstract class WskWebActionsTests extends TestHelpers with WskTestHelpers with RestUtil with BeforeAndAfterAll {
   val MAX_URL_LENGTH = 8192 // 8K matching nginx default
 
-  val wsk = new Wsk
+  val wsk: BaseWsk
   private implicit val wskprops = WskProps()
   val namespace = wsk.namespace.whois()
 
@@ -270,10 +270,8 @@ class WskWebActionsTests extends TestHelpers with WskTestHelpers with RestUtil w
 
     response.statusCode shouldBe 200
     val cookieHeaders = response.headers.getList("Set-Cookie")
-    cookieHeaders should contain allOf (
-      new Header("Set-Cookie", "a=b"),
-      new Header("Set-Cookie", "c=d")
-    )
+    cookieHeaders should contain allOf (new Header("Set-Cookie", "a=b"),
+    new Header("Set-Cookie", "c=d"))
   }
 
   it should "handle http web action with base64 encoded response" in withAssetCleaner(wskprops) { (wp, assetHelper) =>
diff --git a/tools/travis/test_openwhisk.sh b/tools/travis/test_openwhisk.sh
index c2bcc2cf..1dfb5139 100755
--- a/tools/travis/test_openwhisk.sh
+++ b/tools/travis/test_openwhisk.sh
@@ -23,7 +23,8 @@ WHISKDIR="$HOMEDIR/incubator-openwhisk"
 cd $WHISKDIR
 ./tools/travis/setup.sh
 
-ANSIBLE_CMD="ansible-playbook -i environments/local -e docker_image_prefix=openwhisk"
+ANSIBLE_CMD="ansible-playbook -i environments/local -e docker_image_prefix=testing"
+TERM=dumb ./gradlew distDocker -PdockerImagePrefix=testing
 
 cd $WHISKDIR/ansible
 $ANSIBLE_CMD setup.yml
@@ -46,12 +47,12 @@ cp $TRAVIS_BUILD_DIR/bin/wsk $WHISKDIR/bin
 # Run the test cases under openwhisk to ensure the quality of the binary.
 cd $TRAVIS_BUILD_DIR
 
-./gradlew :tests:test -Dtest.single=*ApiGwTests*
+./gradlew :tests:test -Dtest.single=*ApiGwCliTests*
 sleep 30
-./gradlew :tests:test -Dtest.single=*ApiGwRoutemgmtActionTests*
+./gradlew :tests:test -Dtest.single=*ApiGwCliRoutemgmtActionTests*
 sleep 30
-./gradlew :tests:test -Dtest.single=*ApiGwEndToEndTests*
+./gradlew :tests:test -Dtest.single=*ApiGwCliEndToEndTests*
 sleep 30
-./gradlew :tests:test -Dtest.single=Wsk*Tests*
+./gradlew :tests:test -Dtest.single=*Wsk*Tests*
 
 make integration_test
diff --git a/wski18n/i18n_resources.go b/wski18n/i18n_resources.go
index 1bccb9fd..6c0f4317 100644
--- a/wski18n/i18n_resources.go
+++ b/wski18n/i18n_resources.go
@@ -109,7 +109,7 @@ func wski18nResourcesDe_deAllJson() (*asset, error) {
         return nil, err
     }
 
-    info := bindataFileInfo{name: "wski18n/resources/de_DE.all.json", size: 0, mode: os.FileMode(420), modTime: time.Unix(1508369189, 0)}
+    info := bindataFileInfo{name: "wski18n/resources/de_DE.all.json", size: 0, mode: os.FileMode(420), modTime: time.Unix(1510606284, 0)}
     a := &asset{bytes: bytes, info: info}
     return a, nil
 }
@@ -129,7 +129,7 @@ func wski18nResourcesEn_usAllJson() (*asset, error) {
         return nil, err
     }
 
-    info := bindataFileInfo{name: "wski18n/resources/en_US.all.json", size: 51835, mode: os.FileMode(420), modTime: time.Unix(1508369296, 0)}
+    info := bindataFileInfo{name: "wski18n/resources/en_US.all.json", size: 51835, mode: os.FileMode(420), modTime: time.Unix(1510606284, 0)}
     a := &asset{bytes: bytes, info: info}
     return a, nil
 }
@@ -149,7 +149,7 @@ func wski18nResourcesEs_esAllJson() (*asset, error) {
         return nil, err
     }
 
-    info := bindataFileInfo{name: "wski18n/resources/es_ES.all.json", size: 0, mode: os.FileMode(420), modTime: time.Unix(1508369189, 0)}
+    info := bindataFileInfo{name: "wski18n/resources/es_ES.all.json", size: 0, mode: os.FileMode(420), modTime: time.Unix(1510606284, 0)}
     a := &asset{bytes: bytes, info: info}
     return a, nil
 }
@@ -169,7 +169,7 @@ func wski18nResourcesFr_frAllJson() (*asset, error) {
         return nil, err
     }
 
-    info := bindataFileInfo{name: "wski18n/resources/fr_FR.all.json", size: 101, mode: os.FileMode(420), modTime: time.Unix(1508369189, 0)}
+    info := bindataFileInfo{name: "wski18n/resources/fr_FR.all.json", size: 101, mode: os.FileMode(420), modTime: time.Unix(1510606284, 0)}
     a := &asset{bytes: bytes, info: info}
     return a, nil
 }
@@ -189,7 +189,7 @@ func wski18nResourcesIt_itAllJson() (*asset, error) {
         return nil, err
     }
 
-    info := bindataFileInfo{name: "wski18n/resources/it_IT.all.json", size: 0, mode: os.FileMode(420), modTime: time.Unix(1508369189, 0)}
+    info := bindataFileInfo{name: "wski18n/resources/it_IT.all.json", size: 0, mode: os.FileMode(420), modTime: time.Unix(1510606284, 0)}
     a := &asset{bytes: bytes, info: info}
     return a, nil
 }
@@ -209,7 +209,7 @@ func wski18nResourcesJa_jaAllJson() (*asset, error) {
         return nil, err
     }
 
-    info := bindataFileInfo{name: "wski18n/resources/ja_JA.all.json", size: 0, mode: os.FileMode(420), modTime: time.Unix(1508369189, 0)}
+    info := bindataFileInfo{name: "wski18n/resources/ja_JA.all.json", size: 0, mode: os.FileMode(420), modTime: time.Unix(1510606284, 0)}
     a := &asset{bytes: bytes, info: info}
     return a, nil
 }
@@ -229,7 +229,7 @@ func wski18nResourcesKo_krAllJson() (*asset, error) {
         return nil, err
     }
 
-    info := bindataFileInfo{name: "wski18n/resources/ko_KR.all.json", size: 0, mode: os.FileMode(420), modTime: time.Unix(1508369189, 0)}
+    info := bindataFileInfo{name: "wski18n/resources/ko_KR.all.json", size: 0, mode: os.FileMode(420), modTime: time.Unix(1510606284, 0)}
     a := &asset{bytes: bytes, info: info}
     return a, nil
 }
@@ -249,7 +249,7 @@ func wski18nResourcesPt_brAllJson() (*asset, error) {
         return nil, err
     }
 
-    info := bindataFileInfo{name: "wski18n/resources/pt_BR.all.json", size: 0, mode: os.FileMode(420), modTime: time.Unix(1508369189, 0)}
+    info := bindataFileInfo{name: "wski18n/resources/pt_BR.all.json", size: 0, mode: os.FileMode(420), modTime: time.Unix(1510606284, 0)}
     a := &asset{bytes: bytes, info: info}
     return a, nil
 }
@@ -269,7 +269,7 @@ func wski18nResourcesZh_hansAllJson() (*asset, error) {
         return nil, err
     }
 
-    info := bindataFileInfo{name: "wski18n/resources/zh_Hans.all.json", size: 0, mode: os.FileMode(420), modTime: time.Unix(1508369189, 0)}
+    info := bindataFileInfo{name: "wski18n/resources/zh_Hans.all.json", size: 0, mode: os.FileMode(420), modTime: time.Unix(1510606284, 0)}
     a := &asset{bytes: bytes, info: info}
     return a, nil
 }
@@ -289,7 +289,7 @@ func wski18nResourcesZh_hantAllJson() (*asset, error) {
         return nil, err
     }
 
-    info := bindataFileInfo{name: "wski18n/resources/zh_Hant.all.json", size: 0, mode: os.FileMode(420), modTime: time.Unix(1508369189, 0)}
+    info := bindataFileInfo{name: "wski18n/resources/zh_Hant.all.json", size: 0, mode: os.FileMode(420), modTime: time.Unix(1510606284, 0)}
     a := &asset{bytes: bytes, info: info}
     return a, nil
 }


 

----------------------------------------------------------------
This is an automated message from the Apache Git Service.
To respond to the message, please log on GitHub and use the
URL above to go to the specific comment.
 
For queries about this service, please contact Infrastructure at:
users@infra.apache.org


With regards,
Apache Git Services