This is an automated email from the ASF dual-hosted git repository.

zrhoffman pushed a commit to branch master
in repository https://gitbox.apache.org/repos/asf/trafficcontrol.git


The following commit(s) were added to refs/heads/master by this push:
     new 30c0feb  TP tests alert logging (#6120)
30c0feb is described below

commit 30c0feb6904de74517dd927df4c721f342105e57
Author: ocket8888 <[email protected]>
AuthorDate: Fri Aug 20 11:15:17 2021 -0600

    TP tests alert logging (#6120)
    
    * Move `hasProperty` to its own module and provide type-specific property 
checking
    
    * Add a data model for global testing configuration that can define log 
levels
    
    * Make API take a configuration parameter and export an instance globally
    
    * Add common logging of Alerts to all axios requests in the API class
    
    * Update API usage to use new global export
    
    * Fix implicit 'any' type detection in old TS versions
    
    * Put TO logs in artifacts, clear up console output
    
    This also fixes assumptions that asynchronous background jobs will have
    reached a certain point by the time the foreground reaches a certain
    point, and adds debug output to the TO logs. Plus uses `npm` scripts
    instead of raw commands.
    
    * Move Riak logs into artifacts, out of console
    
    * Fix not restoring directory state after starting TP, moving open file 
descriptors
    
    * upload artifacts on all failures, not just test failures
    
    * Fix docker logs leaking stderr
    
    * log full URL
    
    * Wait for TO to start before continuing
    
    * fix passing API requests through TP instead of straight to TO
    
    * fix extra path separator in url building
    
    * Fix incorrect info log, remove commented-out code
    
    * Simplify checking property existence and type with new hasProperty call 
signatures
    
    * Remove redundant option
    
    * change debug logs to stdout
---
 .github/actions/tp-integration-tests/cdn.json      |  10 +-
 .github/actions/tp-integration-tests/entrypoint.sh |  90 ++++-----
 traffic_portal/test/integration/CommonUtils/API.ts | 211 ++++++++++++++-------
 .../test/integration/CommonUtils/index.ts          |  21 ++
 .../test/integration/CommonUtils/utils.ts          | 133 +++++++++++++
 traffic_portal/test/integration/config.model.ts    | 165 ++++++++++++++++
 traffic_portal/test/integration/config.ts          |  21 +-
 traffic_portal/test/integration/specs/ASNs.spec.ts |   3 +-
 .../test/integration/specs/Coordinates.spec.ts     |   3 +-
 .../integration/specs/DeliveryServices.spec.ts     |   5 +-
 .../test/integration/specs/Divisions.spec.ts       |   4 +-
 traffic_portal/test/integration/specs/Jobs.spec.ts |   3 +-
 .../test/integration/specs/Origins.spec.ts         |   3 +-
 .../test/integration/specs/Parameters.spec.ts      |   5 +-
 .../test/integration/specs/PhysLocations.spec.ts   |   3 +-
 .../test/integration/specs/Profiles.spec.ts        |   3 +-
 .../test/integration/specs/Regions.spec.ts         |   3 +-
 .../specs/ServerServerCapabilities.spec.ts         |   3 +-
 .../test/integration/specs/Servers.spec.ts         |   3 +-
 .../integration/specs/ServiceCategories.spec.ts    |   3 +-
 .../test/integration/specs/Statuses.spec.ts        |   3 +-
 .../test/integration/specs/Topologies.spec.ts      |   6 +-
 .../test/integration/specs/Types.spec.ts           |   3 +-
 23 files changed, 554 insertions(+), 153 deletions(-)

diff --git a/.github/actions/tp-integration-tests/cdn.json 
b/.github/actions/tp-integration-tests/cdn.json
index e2ac195..6bd2b68 100644
--- a/.github/actions/tp-integration-tests/cdn.json
+++ b/.github/actions/tp-integration-tests/cdn.json
@@ -14,11 +14,11 @@
        "traffic_ops_golang": {
                "insecure": true,
                "port": "6443",
-               "log_location_error": "error.log",
-               "log_location_warning": "warning.log",
-               "log_location_info": "info.log",
-               "log_location_debug": null,
-               "log_location_event": "event.log",
+               "log_location_error": "stderr",
+               "log_location_warning": "stderr",
+               "log_location_info": "stdout",
+               "log_location_debug": "stdout",
+               "log_location_event": "stdout",
                "max_db_connections": 20,
                "db_conn_max_lifetime_seconds": 60,
                "db_query_timeout_seconds": 20,
diff --git a/.github/actions/tp-integration-tests/entrypoint.sh 
b/.github/actions/tp-integration-tests/entrypoint.sh
index afd3b8f..6e6ca2d 100755
--- a/.github/actions/tp-integration-tests/entrypoint.sh
+++ b/.github/actions/tp-integration-tests/entrypoint.sh
@@ -15,7 +15,35 @@
 # KIND, either express or implied.  See the License for the
 # specific language governing permissions and limitations
 # under the License.
-trap 'echo "Error on line ${LINENO} of ${0}"; exit 1' ERR
+
+onFail() {
+  echo "Error on line ${1} of ${2}" >&2;
+  if ! [[ -d Reports ]]; then
+    mkdir Reports;
+  fi
+  if [[ -f tv.log ]]; then
+    cp tv.log Reports/traffic_vault.docker.log;
+  fi
+       docker logs "$trafficvault" > Reports/traffic_vault.log 2>&1;
+  if [[ -f tp.log ]]; then
+    mv tp.log Reports/forever.log
+  fi
+  if [[ -f access.log ]]; then
+    mv access.log Reports/tp-access.log
+  fi
+  if [[ -f out.log ]]; then
+    mv out.log Reports/node.log
+  fi
+  docker logs $CHROMIUM_CONTAINER > Reports/chromium.log 2>&1;
+  docker logs $HUB_CONTAINER > Reports/hub.log 2>&1;
+  if [[ -f "${REPO_DIR}/traffic_ops/traffic_ops_golang" ]]; then
+    cp "${REPO_DIR}/traffic_ops/traffic_ops_golang" Reports/to.log;
+  fi
+  echo "Detailed logs produced info Reports artifact"
+  exit 1
+}
+
+trap 'onFail "${LINENO}" "${0}"' ERR
 set -o errexit -o nounset -o pipefail
 
 hub_fqdn="http://localhost:4444/wd/hub/status";
@@ -23,7 +51,7 @@ to_fqdn="https://localhost:6443";
 tp_fqdn="https://172.18.0.1:8443";
 
 if ! curl -Lvsk "${hub_fqdn}" >/dev/null 2>&1; then
-  echo "Selenium not started on ${hub_fqdn}"
+  echo "Selenium not started on ${hub_fqdn}" >&2;
   exit 1
 fi
 
@@ -81,19 +109,6 @@ QUERY
 
 sudo useradd trafops
 
-gray_bg="$(printf '%s%s' $'\x1B' '[100m')";
-red_bg="$(printf '%s%s' $'\x1B' '[41m')";
-yellow_bg="$(printf '%s%s' $'\x1B' '[43m')";
-black_fg="$(printf '%s%s' $'\x1B' '[30m')";
-color_and_prefix() {
-       color="$1";
-       shift;
-       prefix="$1";
-       normal_bg="$(printf '%s%s' $'\x1B' '[49m')";
-       normal_fg="$(printf '%s%s' $'\x1B' '[39m')";
-       sed "s/^/${color}${black_fg}${prefix}: /" | sed 
"s/$/${normal_bg}${normal_fg}/";
-}
-
 ciab_dir="${GITHUB_WORKSPACE}/infrastructure/cdn-in-a-box";
 trafficvault=trafficvault;
 start_traffic_vault() {
@@ -127,9 +142,9 @@ start_traffic_vault() {
                --publish=8087:8087 \
                --rm \
                "$trafficvault" \
-               /usr/lib/riak/riak-cluster.sh;
+               /usr/lib/riak/riak-cluster.sh
 }
-start_traffic_vault &
+start_traffic_vault >tv.log 2>&1 &
 
 sudo apt-get install -y --no-install-recommends gettext \
        ruby ruby-dev libc-dev curl \
@@ -154,7 +169,7 @@ if [[ ! -e "$REPO_DIR" ]]; then
 fi
 
 to_build() {
-  cd "${REPO_DIR}/traffic_ops/traffic_ops_golang"
+  pushd "${REPO_DIR}/traffic_ops/traffic_ops_golang"
   if  [[ ! -d "${GITHUB_WORKSPACE}/vendor/golang.org" ]]; then
     go mod vendor
   fi
@@ -167,56 +182,42 @@ to_build() {
 
   export $(<"${ciab_dir}/variables.env" sed '/^#/d') # defines 
TV_ADMIN_USER/PASSWORD
   envsubst <"${resources}/riak.json" >riak.conf
-  truncate --size=0 warning.log error.log event.log info.log
+  truncate -s0 out.log
 
-  ./traffic_ops_golang --cfg ./cdn.conf --dbcfg ./database.conf -riakcfg 
riak.conf &
-  tail -f warning.log 2>&1 | color_and_prefix "${yellow_bg}" 'Traffic Ops 
WARN' &
-  tail -f error.log 2>&1 | color_and_prefix "${red_bg}" 'Traffic Ops ERR' &
-  tail -f event.log 2>&1 | color_and_prefix "${gray_bg}" 'Traffic Ops EVT' &
+  ./traffic_ops_golang --cfg ./cdn.conf --dbcfg ./database.conf -riakcfg 
riak.conf >out.log 2>&1 &
+  popd
 }
 
 tp_build() {
-  cd "${REPO_DIR}/traffic_portal"
+  pushd "${REPO_DIR}/traffic_portal"
   npm ci
   bower install
   grunt dist
 
   cp "${resources}/config.js" ./conf/
   touch tp.log access.log out.log err.log
-  sudo forever --minUptime 5000 --spinSleepTime 2000 -f -o out.log start 
server.js &
-  tail -f err.log 2>&1 | color_and_prefix "${red_bg}" "Node Err" &
+  sudo forever --minUptime 5000 --spinSleepTime 2000 -f start server.js 
>out.log 2>&1 &
+  popd
 }
 
-(to_build) &
-(tp_build) &
-
-onFail() {
-       docker logs "$trafficvault"  > Reports/traffic_vault.log
-  mv tp.log Reports/forever.log
-  mv access.log Reports/tp-access.log
-  mv out.log Reports/node.log
-  docker logs $CHROMIUM_CONTAINER > Reports/chromium.log
-  docker logs $HUB_CONTAINER > Reports/hub.log
-  echo "Detailed logs produced info Reports artifact"
-  exit 1
-}
+to_build
+tp_build
 
 cd "${REPO_DIR}/traffic_portal/test/integration"
 npm ci
-PATH=$(pwd)/node_modules/.bin/:$PATH
 
-webdriver-manager update --gecko false --versions.chrome 
"LATEST_RELEASE_$CHROMIUM_VER"
+./node_modules/.bin/webdriver-manager update --gecko false --versions.chrome 
"LATEST_RELEASE_$CHROMIUM_VER"
 
 jq " .capabilities.chromeOptions.args = [
     \"--headless\",
     \"--no-sandbox\",
     \"--disable-gpu\",
     \"--ignore-certificate-errors\"
-  ] | .params.apiUrl = \"${tp_fqdn}/api/4.0\" | .params.baseUrl =\"${tp_fqdn}\"
+  ] | .params.apiUrl = \"${to_fqdn}/api/4.0\" | .params.baseUrl =\"${tp_fqdn}\"
   | .capabilities[\"goog:chromeOptions\"].w3c = false | 
.capabilities.chromeOptions.w3c = false" \
   config.json > config.json.tmp && mv config.json.tmp config.json
 
-tsc
+npm run build
 
 # Wait for tp/to build
 timeout 5m bash <<TMOUT
@@ -226,5 +227,4 @@ timeout 5m bash <<TMOUT
   done
 TMOUT
 
-trap - ERR
-protractor ./GeneratedCode/config.js --params.baseUrl="${tp_fqdn}" 
--params.apiUrl="${to_fqdn}/api/4.0" || onFail
+npm test -- --params.baseUrl="${tp_fqdn}" --params.apiUrl="${to_fqdn}/api/4.0"
diff --git a/traffic_portal/test/integration/CommonUtils/API.ts 
b/traffic_portal/test/integration/CommonUtils/API.ts
index 9b18768..2a626a3 100644
--- a/traffic_portal/test/integration/CommonUtils/API.ts
+++ b/traffic_portal/test/integration/CommonUtils/API.ts
@@ -21,10 +21,12 @@
 import { Agent } from "https";
 
 import axios from 'axios';
-import type {AxiosResponse} from "axios";
+import type {AxiosResponse, AxiosError} from "axios";
 import randomIpv6 from "random-ipv6";
 
-import { config, randomize } from '../config';
+import { hasProperty } from "./utils";
+import { randomize } from '../config';
+import { AlertLevel, isAlert, logAlert, TestingConfig } from "../config.model";
 
 interface GetRequest {
     queryKey: string;
@@ -50,33 +52,61 @@ export interface APIData {
 }
 
 /**
- * hasProperty checks, generically, whether some variable passed as `o` has the
- * property `k`.
+ * Checks if an object is an AxiosError, usually useful in `try`/`catch` blocks
+ * around axios calls.
  *
- * @example
- * hasProperty({}, "id"); // returns false
- * hasProperty({id: 8}); // returns true
- * hasProperty({id: undefined}); // returns true
- *
- * @param o The object to check.
- * @param k The key for which to check in the object.
- * @returns Whether or not `o` has the property `k`.
- * @throws {Error} when the type check fails.
+ * @param e The object to check.
+ * @returns Whether or not `e` is an AxiosError.
  */
- export function hasProperty<T extends object, K extends PropertyKey, S>(o: T, 
k: K): o is T & Record<K, S | unknown> {
-       return Object.prototype.hasOwnProperty.call(o, k);
+function isAxiosError(e: unknown): e is AxiosError {
+    if (typeof(e) !== "object" || e === null) {
+        return false;
+    }
+    if (!hasProperty(e, "isAxiosError", "boolean")) {
+        return false;
+    }
+    return e.isAxiosError;
 }
 
 export class API {
 
     private cookie = "";
-    private readonly config = config;
 
-    constructor() {
+    /**
+     * This controls the alert levels that get logged - levels not in this set
+     * are not logged
+     */
+    private readonly alertLevels = new Set<AlertLevel>(["warning", "error", 
"info"]);
+    /**
+     * Stores login information for the admin-level user.
+     */
+    private readonly loginInfo: {
+        password: string;
+        username: string;
+    };
+    /**
+     * The URL base used for the Traffic Ops API.
+     *
+     * Trailing `/` is guaranteed.
+     *
+     * @example
+     * "https://localhost:6443/api/4.0/";
+     */
+    private readonly apiURL: string;
+
+    /**
+     * @param cfg The testing configuration.
+     */
+    constructor(cfg: TestingConfig) {
         axios.defaults.headers.common['Accept'] = 'application/json'
         axios.defaults.headers.common['Authorization'] = 'No-Auth'
         axios.defaults.headers.common['Content-Type'] = 'application/json'
         axios.defaults.httpsAgent = new Agent({ rejectUnauthorized: false })
+        if (cfg.alertLevels) {
+            this.alertLevels = new Set(cfg.alertLevels);
+        }
+        this.loginInfo = cfg.login;
+        this.apiURL = cfg.apiUrl.endsWith("/") ? cfg.apiUrl : `${cfg.apiUrl}/`;
     }
 
     /**
@@ -86,18 +116,90 @@ export class API {
      * @throws {Error} when login fails, or when Traffic Ops doesn't return a 
cookie.
      */
     public async Login(): Promise<AxiosResponse<unknown>> {
-        const response = await axios({
-            method: 'post',
-            url: this.config.params.apiUrl + '/user/login',
-            data: {
-                u: this.config.params.login.username,
-                p: this.config.params.login.password
-            }
-        });
-        this.cookie = await response.headers["set-cookie"][0];
+        const data = {
+            p: this.loginInfo.password,
+            u: this.loginInfo.username,
+        }
+        const response = await this.getResponse("post", "/user/login", data);
+        const h = response.headers as object;
+        if (!hasProperty(h, "set-cookie", "Array") || h["set-cookie"].length < 
1) {
+            throw new Error("Traffic Ops response did not set a cookie");
+        }
+        const cookie = await h["set-cookie"][0];
+        if (typeof(cookie) !== "string") {
+            throw new Error(`non-string cookie: ${cookie}`);
+        }
+        this.cookie = cookie;
         return response
     }
 
+    /**
+     * Retrieves a response from the API.
+     *
+     * Alerts will be logged if they are found - even if an error occurs and is
+     * thrown.
+     *
+     * @param method The request method to use.
+     * @param path The path to request, relative to the configured TO API URL.
+     * @returns The server's response.
+     * @throws {unknown} when the request fails for any reason. If an error
+     * response was returned from the API, it was logged, so there's no need to
+     * dig into the properties of these errors, really.
+     */
+    private async getResponse(method: "get" | "delete", path: string): 
Promise<AxiosResponse>;
+    /**
+     * Retrieves a response from the API.
+     *
+     * Alerts will be logged if they are found - even if an error occurs and is
+     * thrown.
+     *
+     * @param method The request method to use.
+     * @param path The path to request, relative to the configured TO API URL.
+     * @param data Data to send in the body of the POST request.
+     * @returns The server's response.
+     * @throws {unknown} when the request fails for any reason. If an error
+     * response was returned from the API, it was logged, so there's no need to
+     * dig into the properties of these errors, really.
+     */
+    private async getResponse(method: "post", path: string, data: unknown): 
Promise<AxiosResponse>;
+    private async getResponse(method: "post" | "get" | "delete", path: string, 
data?: unknown): Promise<AxiosResponse> {
+        if (method === "post" && data === undefined) {
+            throw new TypeError("request body must be given for POST 
requests");
+        }
+
+        const url = `${this.apiURL}${path.replace(/^\/+/g, "")}`;
+        const conf = {
+            method,
+            url,
+            headers: { Cookie: this.cookie },
+            data
+        }
+
+        let throwable;
+        let resp: AxiosResponse<unknown>;
+        try {
+            resp = await axios(conf);
+        } catch(e) {
+            if (!isAxiosError(e) || !e.response) {
+                console.debug("non-axios error or axios error with no response 
thrown");
+                throw e;
+            }
+            resp = e.response;
+            throwable = e;
+        }
+        if (typeof(resp.data) === "object" && resp.data !== null && 
hasProperty(resp.data, "alerts", "Array")) {
+            for (const a of resp.data.alerts) {
+                if (isAlert(a) && this.alertLevels.has(a.level)) {
+                    logAlert(a, `${method.toUpperCase()} ${url} 
(${resp.status} ${resp.statusText}):`);
+                }
+            }
+        }
+        if (throwable) {
+            throw throwable;
+        }
+        return resp;
+    }
+
     public async SendRequest<T extends IDData>(route: string, method: string, 
data: T): Promise<void> {
         let response
         this.Randomize(data)
@@ -117,19 +219,10 @@ export class API {
 
         switch (method) {
             case "post":
-                response = await axios({
-                    method: method,
-                    url: this.config.params.apiUrl + route,
-                    headers: { Cookie: this.cookie},
-                    data: data
-                });
+                response = await this.getResponse("post", route, data);
                 break;
             case "get":
-                response = await axios({
-                    method: method,
-                    url: this.config.params.apiUrl + route,
-                    headers: { Cookie: this.cookie},
-                });
+                response = await this.getResponse("get", route);
                 break;
             case "delete":
                 if (!data.route) {
@@ -147,11 +240,7 @@ export class API {
                 if((data.route).includes('/service_categories/')){
                     data.route = data.route + randomize
                 }
-                response = await axios({
-                    method: method,
-                    url: this.config.params.apiUrl + data.route,
-                    headers: { Cookie: this.cookie},
-                });
+                response = await this.getResponse("delete", data.route);
                 break;
             default:
                 throw new Error(`unrecognized request method: '${method}'`);
@@ -171,11 +260,7 @@ export class API {
         }
         for (const request of data.getRequest) {
             const query = 
`?${encodeURIComponent(request.queryKey)}=${encodeURIComponent(request.queryValue)}${randomize}`;
-            const response = await axios({
-                method: 'get',
-                url: this.config.params.apiUrl + request.route + query,
-                headers: { Cookie: this.cookie},
-            });
+            const response = await this.getResponse("get", request.route + 
query)
 
             if (response.status == 200) {
                 if(request.hasOwnProperty('isArray')){
@@ -193,7 +278,7 @@ export class API {
         return null
     }
 
-   public Randomize(data: object): void {
+    public Randomize(data: object): void {
        if (hasProperty(data, "fullName")) {
            if (hasProperty(data, "email")) {
                data.email = data.fullName + randomize + data.email;
@@ -231,23 +316,19 @@ export class API {
         if(hasProperty(data, 'domainName')) {
             data.domainName = data.domainName + randomize;
         }
-        if(hasProperty(data, 'nodes')){
-            if (data.nodes instanceof Array) {
-                data.nodes.map(i => {
-                    if (typeof(i) === "object" && i !== null && hasProperty(i, 
"cachegroup")) {
-                        i.cachegroup = i.cachegroup + randomize;
-                    }
-                });
-            }
+        if(hasProperty(data, 'nodes', "Array")){
+            data.nodes.map(i => {
+                if (typeof(i) === "object" && i !== null && hasProperty(i, 
"cachegroup")) {
+                    i.cachegroup = i.cachegroup + randomize;
+                }
+            });
         }
-        if(hasProperty(data, 'interfaces')){
-            if (data.interfaces instanceof Array) {
-                const ipv6 = randomIpv6();
-                for (const inf of data.interfaces) {
-                    if (typeof(inf) === "object" && inf !== null && 
hasProperty(inf, "ipAddresses") && inf.ipAddresses instanceof Array) {
-                        for (const ip of inf.ipAddresses) {
-                           ip.address = ipv6.toString();
-                        }
+        if(hasProperty(data, 'interfaces', "Array")){
+            const ipv6 = randomIpv6();
+            for (const inf of data.interfaces) {
+                if (typeof(inf) === "object" && inf !== null && 
hasProperty(inf, "ipAddresses", "Array")) {
+                    for (const ip of inf.ipAddresses) {
+                        (ip as Record<"address", string>).address = 
ipv6.toString();
                     }
                 }
             }
@@ -273,7 +354,7 @@ export class API {
                 }
             }
         } else if (response.status == undefined) {
-            throw new Error(`Error requesting ${this.config.params.apiUrl}: 
${response}`);
+            throw new Error(`Error requesting ${this.apiURL}: ${response}`);
         } else {
             throw new Error(`Login failed: Response Status: 
'${response.statusText}'' Response Data: '${response.data}'`);
         }
diff --git a/traffic_portal/test/integration/CommonUtils/index.ts 
b/traffic_portal/test/integration/CommonUtils/index.ts
new file mode 100644
index 0000000..1cd793c
--- /dev/null
+++ b/traffic_portal/test/integration/CommonUtils/index.ts
@@ -0,0 +1,21 @@
+/*
+ * 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.
+ */
+
+export * from "./API";
+export * from "./utils";
diff --git a/traffic_portal/test/integration/CommonUtils/utils.ts 
b/traffic_portal/test/integration/CommonUtils/utils.ts
new file mode 100644
index 0000000..5b3b6ac
--- /dev/null
+++ b/traffic_portal/test/integration/CommonUtils/utils.ts
@@ -0,0 +1,133 @@
+/*
+ * 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.
+ */
+
+/**
+ * hasProperty checks whether some variable passed as `o` has the string
+ * property `k`.
+ *
+ * @example
+ * hasProperty({}, "id", "string"); // returns false
+ * hasProperty({id: 8}, "id", "string"); // returns false
+ * hasProperty({id: undefined}, "id", "string"); // returns false
+ *
+ * @param o The object to check.
+ * @param k The key for which to check in the object.
+ * @param type Specifies that `o.k` should be a string.
+ * @returns Whether or not `o` has the string property `k`.
+ */
+export function hasProperty<T extends object, K extends PropertyKey>(o: T, k: 
K, type: "string"): o is T & Record<K, string>;
+/**
+ * hasProperty checks whether some variable passed as `o` has the number
+ * property `k`.
+ *
+ * @example
+ * hasProperty({}, "id", "number"); // returns false
+ * hasProperty({id: 8}, "id", "number"); // returns true
+ * hasProperty({id: undefined}, "id", "number"); // returns false
+ *
+ * @param o The object to check.
+ * @param k The key for which to check in the object.
+ * @param type Specifies that `o.k` should be a number.
+ * @returns Whether or not `o` has the number property `k`.
+ */
+export function hasProperty<T extends object, K extends PropertyKey>(o: T, k: 
K, type: "number"): o is T & Record<K, number>;
+/**
+ * hasProperty checks whether some variable passed as `o` has the boolean
+ * property `k`.
+ *
+ * @example
+ * hasProperty({}, "id", "boolean"); // returns false
+ * hasProperty({id: 8}, "id", "boolean"); // returns false
+ * hasProperty({id: undefined}, "id", "boolean"); // returns false
+ * hasProperty({id: true}, "id", "boolean"); // returns true
+ *
+ * @param o The object to check.
+ * @param k The key for which to check in the object.
+ * @param type Specifies that `o.k` should be a boolean.
+ * @returns Whether or not `o` has the boolean property `k`.
+ */
+export function hasProperty<T extends object, K extends PropertyKey>(o: T, k: 
K, type: "boolean"): o is T & Record<K, boolean>;
+/**
+ * hasProperty checks whether some variable passed as `o` has the Array 
property
+ * `k`.
+ *
+ * @example
+ * hasProperty({}, "id", "Array"); // returns false
+ * hasProperty({id: 8}, "id", "Array"); // returns false
+ * hasProperty({id: undefined}, "id", "Array"); // returns false
+ * hasProperty({id: []}, "id", "Array"); // returns true
+ * hasProperty({id: [undefined, null, -7, true]}, "id", "Array"); // returns 
true
+ *
+ * @param o The object to check.
+ * @param k The key for which to check in the object.
+ * @param type Specifies that `o.k` should be a (potentially non-homogenous)
+ * Array.
+ * @returns Whether or not `o` has the Array property `k`.
+ */
+export function hasProperty<T extends object, K extends PropertyKey>(o: T, k: 
K, type: "Array"): o is T & Record<K, Array<unknown>>;
+/**
+ * hasProperty checks, generically, whether some variable passed as `o` has the
+ * property `k`.
+ *
+ * @example
+ * hasProperty({}, "id"); // returns false
+ * hasProperty({id: 8}, "id"); // returns true
+ * hasProperty({id: undefined}, "id"); // returns true
+ *
+ * @param o The object to check.
+ * @param k The key for which to check in the object.
+ * @returns Whether or not `o` has the property `k`.
+ */
+export function hasProperty<T extends object, K extends PropertyKey>(o: T, k: 
K): o is T & Record<K, unknown>;
+/**
+ * hasProperty checks, generically, whether some variable passed as `o` has the
+ * property `k`.
+ *
+ * @example
+ * hasProperty({}, "id"); // returns false
+ * hasProperty({id: 8}, "id"); // returns true
+ * hasProperty({id: undefined}, "id"); // returns true
+ * hasProperty({id: 8}, "id", "number"); // returns true
+ * hasProperty({id: undefined}, "id", "number"); // returns false
+ * hasProperty({id: 8}, "id", "string"); // returns false
+ *
+ * @param o The object to check.
+ * @param k The key for which to check in the object.
+ * @param type Optionally specify a type to check for.
+ * @returns Whether or not `o` has the property `k`.
+ */
+export function hasProperty<T extends object, K extends PropertyKey, S>(o: T, 
k: K, type?: "string" | "number" | "boolean" | "Array"): o is T & Record<K, S> {
+       if (!Object.prototype.hasOwnProperty.call(o, k)) {
+               return false;
+       }
+       if (!type) {
+               return true;
+       }
+       const val = (o as Record<K, unknown>)[k];
+       switch (type) {
+               case "string":
+                       return typeof(val) === "string";
+               case "number":
+                       return typeof(val) === "number";
+               case "boolean":
+                       return typeof(val) === "boolean";
+               case "Array":
+                       return val instanceof Array;
+       }
+}
diff --git a/traffic_portal/test/integration/config.model.ts 
b/traffic_portal/test/integration/config.model.ts
new file mode 100644
index 0000000..9895820
--- /dev/null
+++ b/traffic_portal/test/integration/config.model.ts
@@ -0,0 +1,165 @@
+/*
+ * 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 { hasProperty } from "./CommonUtils/utils";
+
+/** Possible levels of TO Alerts */
+export type AlertLevel = "success" | "info" | "warning" | "error";
+
+/**
+ * Checks whether an object is a valid Alert level.
+ *
+ * @param s The object to check.
+ * @returns Whether or not `s` is an AlertLevel.
+ */
+export function isAlertLevel(s: unknown): s is AlertLevel {
+    if (typeof(s) !== "string") {
+        return false;
+    }
+    switch (s) {
+        case "success":
+        case "info":
+        case "warning":
+        case "error":
+            return true;
+    }
+    return false;
+}
+
+/** TO API Alerts */
+export interface Alert {
+    level: AlertLevel;
+    text: string;
+}
+
+/**
+ * Checks whether an object is an Alert like the ones TO normally returns.
+ *
+ * @param a The object to check.
+ * @returns Whether or not `a` is an Alert.
+ */
+export function isAlert(a: unknown): a is Alert {
+    if (typeof(a) !== "object" || a === null) {
+        return false;
+    }
+    if (!hasProperty(a, "level") || !hasProperty(a, "text", "string")) {
+        return false;
+    }
+    return isAlertLevel(a.level);
+}
+
+/**
+ * Logs an alert to the appropriate console stream based on its `level`.
+ *
+ * @param a The Alert to log.
+ * @param prefix Optional prefix for the log message
+ */
+export function logAlert(a: Alert, prefix?: string): void {
+    let logfunc;
+    let pre = (prefix ?? "").trimStart();
+    switch (a.level) {
+        case "success":
+            logfunc = console.log;
+            pre = `SUCCESS: ${pre}`;
+            break;
+        case "info":
+            logfunc = console.info
+            pre = `INFO: ${pre}`;
+            break;
+        case "warning":
+            logfunc = console.warn
+            pre = `WARN: ${pre}`;
+            break;
+        case "error":
+            logfunc = console.error
+            pre = `ERROR: ${pre}`;
+            break;
+    }
+    logfunc(pre, a.text);
+}
+
+/** TestingConfig is the type of a testing configuration. */
+export interface TestingConfig {
+       /** This is login information for a user with admin-level permissions. 
*/
+       readonly login: {
+               readonly password: string;
+               readonly username: string;
+       };
+       /** The URL at which the Traffic Ops API can be accessed. */
+       readonly apiUrl: string;
+       /** The URL at which Traffic Portal is served - root path. */
+       readonly baseUrl: string;
+       /** Logging alert levels that are enabled. */
+       readonly alertLevels?: Array<AlertLevel>;
+}
+
+/**
+ * Checks if a passed object is a valid testing configuration.
+ *
+ * @param c The object to check.
+ * @returns `true`, always. When the check fails, it throws an error that
+ * explains why.
+ */
+export function isTestingConfig(c: unknown): c is TestingConfig {
+       if (typeof(c) !== "object") {
+               throw new Error(`testing configuration must be an object, not a 
'${typeof(c)}'`);
+       }
+       if (c === null) {
+               throw new Error("testing configuration must be an object, not 
'null'");
+       }
+
+       if (!hasProperty(c, "login") || typeof(c.login) !== "object" || c.login 
=== null) {
+               throw new Error("missing or invalid 'login' property");
+       }
+       if (!hasProperty(c.login, "password", "string") || 
!hasProperty(c.login, "username", "string")) {
+               throw new Error("'login' property has missing and/or invalid 
'password' and/or 'username' property(ies)");
+       }
+       if (c.login.username === "" || c.login.password === "") {
+               throw new Error("neither 'login.username' nor 'login.password' 
may be blank");
+       }
+       if (!hasProperty(c, "apiUrl", "string")) {
+               throw new Error("missing or invalid 'apiUrl' property");
+       }
+       try {
+               new URL(c.apiUrl);
+       } catch (e) {
+               throw new Error(`'apiUrl' is not a valid URL: ${c.apiUrl}`);
+       }
+       let baseURL;
+       if (!hasProperty(c, "baseUrl", "string")) {
+               throw new Error("missing or invalid 'baseUrl' property");
+       }
+       try {
+               baseURL = new URL(c.baseUrl);
+       } catch (e) {
+               throw new Error(`'baseUrl' is not a valid URL: ${c.baseUrl}`);
+       }
+       if (baseURL.pathname !== "/") {
+               throw new Error("'baseUrl' must be a root path");
+       }
+       if (!hasProperty(c, "alertLevels")) {
+               return true;
+       }
+       if (!(c.alertLevels instanceof Array)) {
+               throw new Error("'alertLevels' must be an array");
+       }
+       if (!c.alertLevels.every(isAlertLevel)) {
+               throw new Error(`invalid alert levels: ${c.alertLevels}`);
+       }
+       return true;
+}
diff --git a/traffic_portal/test/integration/config.ts 
b/traffic_portal/test/integration/config.ts
index 96ccc55..388d14b 100644
--- a/traffic_portal/test/integration/config.ts
+++ b/traffic_portal/test/integration/config.ts
@@ -23,9 +23,10 @@ import { Config, browser } from 'protractor';
 import { JUnitXmlReporter } from 'jasmine-reporters';
 import HtmlReporter from "protractor-beautiful-reporter";
 
-import { API } from './CommonUtils/API';
+import { API } from './CommonUtils';
 import * as conf from "./config.json"
 import { prerequisites } from "./Data";
+import { isTestingConfig } from "./config.model";
 
 const downloadsPath = resolve('Downloads');
 export const randomize = Math.random().toString(36).substring(3, 7);
@@ -37,6 +38,23 @@ if (config.capabilities) {
 } else {
   config.capabilities = {chromeOptions: {prefs: {download: {default_directory: 
downloadsPath}}}};
 }
+
+if (!config.params) {
+  throw new Error("no testing parameters provided - cannot proceed");
+}
+
+try {
+  if (!isTestingConfig(config.params)) {
+    throw new Error();
+  }
+} catch (e) {
+  const msg = e instanceof Error ? e.message : String(e);
+  throw new Error(`invalid testing params: ${msg}`);
+}
+
+export const testingConfig = config.params;
+export const api = new API(testingConfig);
+
 config.onPrepare = async function () {
     await browser.waitForAngularEnabled(true);
     await browser.driver.manage().window().maximize();
@@ -66,6 +84,5 @@ config.onPrepare = async function () {
         }).getJasmine2Reporter());
     }
 
-    const api = new API();
     await api.UseAPI(prerequisites);
 }
diff --git a/traffic_portal/test/integration/specs/ASNs.spec.ts 
b/traffic_portal/test/integration/specs/ASNs.spec.ts
index a3761c8..3359931 100644
--- a/traffic_portal/test/integration/specs/ASNs.spec.ts
+++ b/traffic_portal/test/integration/specs/ASNs.spec.ts
@@ -20,11 +20,10 @@ import { browser } from 'protractor';
 
 import { LoginPage } from '../PageObjects/LoginPage.po';
 import { TopNavigationPage } from '../PageObjects/TopNavigationPage.po';
-import { API } from '../CommonUtils/API';
+import { api } from "../config";
 import { ASNsPage } from '../PageObjects/ASNs.po';
 import { ASNs } from "../Data";
 
-const api = new API();
 const loginPage = new LoginPage();
 const topNavigation = new TopNavigationPage();
 const asnsPage = new ASNsPage();
diff --git a/traffic_portal/test/integration/specs/Coordinates.spec.ts 
b/traffic_portal/test/integration/specs/Coordinates.spec.ts
index cb6c924..a617130 100644
--- a/traffic_portal/test/integration/specs/Coordinates.spec.ts
+++ b/traffic_portal/test/integration/specs/Coordinates.spec.ts
@@ -20,11 +20,10 @@ import { browser } from 'protractor';
 
 import { LoginPage } from '../PageObjects/LoginPage.po'
 import { CoordinatesPage } from '../PageObjects/CoordinatesPage.po';
-import { API } from '../CommonUtils/API';
+import { api } from "../config";
 import { TopNavigationPage } from '../PageObjects/TopNavigationPage.po';
 import { coordinates } from "../Data";
 
-const api = new API();
 const loginPage = new LoginPage();
 const topNavigation = new TopNavigationPage();
 const coordinatesPage = new CoordinatesPage();
diff --git a/traffic_portal/test/integration/specs/DeliveryServices.spec.ts 
b/traffic_portal/test/integration/specs/DeliveryServices.spec.ts
index 2ba4a69..7c92742 100644
--- a/traffic_portal/test/integration/specs/DeliveryServices.spec.ts
+++ b/traffic_portal/test/integration/specs/DeliveryServices.spec.ts
@@ -21,10 +21,9 @@ import { browser } from 'protractor';
 import { LoginPage } from '../PageObjects/LoginPage.po'
 import { DeliveryServicePage } from '../PageObjects/DeliveryServicePage.po';
 import { TopNavigationPage } from '../PageObjects/TopNavigationPage.po';
-import { API } from '../CommonUtils/API';
+import { api } from "../config";
 import { deliveryservices } from "../Data/deliveryservices";
 
-const api = new API();
 const topNavigation = new TopNavigationPage();
 const loginPage = new LoginPage();
 const deliveryservicesPage = new DeliveryServicePage();
@@ -66,7 +65,7 @@ deliveryservices.tests.forEach(async deliveryservicesData => {
                     expect(await 
deliveryservicesPage.AssignServerToDeliveryService(assignserver)).toBe(true);
                     await deliveryservicesPage.OpenDeliveryServicePage();
                 }
-                
+
                 )
             })
             deliveryservicesData.assignrequiredcapabilities.forEach(assignrc 
=> {
diff --git a/traffic_portal/test/integration/specs/Divisions.spec.ts 
b/traffic_portal/test/integration/specs/Divisions.spec.ts
index 5028c4e..27f5342 100644
--- a/traffic_portal/test/integration/specs/Divisions.spec.ts
+++ b/traffic_portal/test/integration/specs/Divisions.spec.ts
@@ -20,11 +20,11 @@ import { browser } from 'protractor';
 
 import { LoginPage } from '../PageObjects/LoginPage.po';
 import { TopNavigationPage } from '../PageObjects/TopNavigationPage.po';
-import { API } from '../CommonUtils/API';
+import { api } from "../config";
 import { DivisionsPage } from '../PageObjects/Divisions.po';
 import { divisions } from "../Data";
 
-const api = new API();
+
 const loginPage = new LoginPage();
 const topNavigation = new TopNavigationPage();
 const divisionsPage = new DivisionsPage();
diff --git a/traffic_portal/test/integration/specs/Jobs.spec.ts 
b/traffic_portal/test/integration/specs/Jobs.spec.ts
index ba7b8c1..9a0973a 100644
--- a/traffic_portal/test/integration/specs/Jobs.spec.ts
+++ b/traffic_portal/test/integration/specs/Jobs.spec.ts
@@ -18,13 +18,12 @@
  */
 
 import { browser } from 'protractor';
-import { API } from '../CommonUtils/API';
+import { api } from "../config";
 import { LoginPage } from '../PageObjects/LoginPage.po';
 import { TopNavigationPage } from '../PageObjects/TopNavigationPage.po';
 import { JobsPage } from '../PageObjects/Jobs.po'
 import { jobs } from '../Data/jobs';
 
-const api = new API();
 const loginPage = new LoginPage();
 const topNavigation = new TopNavigationPage();
 const jobsPage = new JobsPage();
diff --git a/traffic_portal/test/integration/specs/Origins.spec.ts 
b/traffic_portal/test/integration/specs/Origins.spec.ts
index 20ead35..974be82 100644
--- a/traffic_portal/test/integration/specs/Origins.spec.ts
+++ b/traffic_portal/test/integration/specs/Origins.spec.ts
@@ -21,10 +21,9 @@ import { browser } from 'protractor';
 import { LoginPage } from '../PageObjects/LoginPage.po'
 import { OriginsPage } from '../PageObjects/OriginsPage.po';
 import { TopNavigationPage } from '../PageObjects/TopNavigationPage.po';
-import { API } from '../CommonUtils/API';
+import { api } from "../config";
 import { origins } from "../Data";
 
-const api = new API();
 const loginPage = new LoginPage();
 const topNavigation = new TopNavigationPage();
 const originsPage = new OriginsPage();
diff --git a/traffic_portal/test/integration/specs/Parameters.spec.ts 
b/traffic_portal/test/integration/specs/Parameters.spec.ts
index 95b8c59..04c6c6c 100644
--- a/traffic_portal/test/integration/specs/Parameters.spec.ts
+++ b/traffic_portal/test/integration/specs/Parameters.spec.ts
@@ -20,11 +20,10 @@ import { browser } from 'protractor';
 
 import { LoginPage } from '../PageObjects/LoginPage.po'
 import { ParametersPage } from '../PageObjects/ParametersPage.po';
-import { API } from '../CommonUtils/API';
+import { api } from "../config";
 import { TopNavigationPage } from '../PageObjects/TopNavigationPage.po';
 import { parameters } from "../Data";
 
-const api = new API();
 const loginPage = new LoginPage();
 const topNavigation = new TopNavigationPage();
 const parametersPage = new ParametersPage();
@@ -63,7 +62,7 @@ parameters.tests.forEach(async parametersData => {
                         expect(await 
parametersPage.ToggleTableColumn(toggle.Name)).toBe(true);
                         await parametersPage.OpenParametersPage();
                     }
-                    
+
                 });
             })
             parametersData.add.forEach(add => {
diff --git a/traffic_portal/test/integration/specs/PhysLocations.spec.ts 
b/traffic_portal/test/integration/specs/PhysLocations.spec.ts
index 24bed71..c221fb8 100644
--- a/traffic_portal/test/integration/specs/PhysLocations.spec.ts
+++ b/traffic_portal/test/integration/specs/PhysLocations.spec.ts
@@ -20,11 +20,10 @@ import { browser } from 'protractor';
 
 import { LoginPage } from '../PageObjects/LoginPage.po'
 import { PhysLocationsPage } from '../PageObjects/PhysLocationsPage.po';
-import { API } from '../CommonUtils/API';
+import { api } from "../config";
 import { TopNavigationPage } from '../PageObjects/TopNavigationPage.po';
 import { physLocations } from "../Data";
 
-const api = new API();
 const loginPage = new LoginPage();
 const topNavigation = new TopNavigationPage();
 const physlocationsPage = new PhysLocationsPage();
diff --git a/traffic_portal/test/integration/specs/Profiles.spec.ts 
b/traffic_portal/test/integration/specs/Profiles.spec.ts
index 5878b85..f415db0 100644
--- a/traffic_portal/test/integration/specs/Profiles.spec.ts
+++ b/traffic_portal/test/integration/specs/Profiles.spec.ts
@@ -21,10 +21,9 @@ import { browser } from 'protractor'
 import { LoginPage } from '../PageObjects/LoginPage.po'
 import { ProfilesPage } from '../PageObjects/ProfilesPage.po';
 import { TopNavigationPage } from '../PageObjects/TopNavigationPage.po';
-import { API } from '../CommonUtils/API';
+import { api } from "../config";
 import { profiles } from "../Data";
 
-const api = new API();
 const loginPage = new LoginPage();
 const topNavigation = new TopNavigationPage();
 const profilesPage = new ProfilesPage();
diff --git a/traffic_portal/test/integration/specs/Regions.spec.ts 
b/traffic_portal/test/integration/specs/Regions.spec.ts
index 4380c7a..82b768e 100644
--- a/traffic_portal/test/integration/specs/Regions.spec.ts
+++ b/traffic_portal/test/integration/specs/Regions.spec.ts
@@ -20,11 +20,10 @@ import { browser } from 'protractor';
 
 import { LoginPage } from '../PageObjects/LoginPage.po';
 import { TopNavigationPage } from '../PageObjects/TopNavigationPage.po';
-import { API } from '../CommonUtils/API';
+import { api } from "../config";
 import { RegionsPage } from '../PageObjects/RegionsPage.po';
 import { regions } from "../Data";
 
-const api = new API();
 const loginPage = new LoginPage();
 const topNavigation = new TopNavigationPage();
 const regionsPage = new RegionsPage();
diff --git 
a/traffic_portal/test/integration/specs/ServerServerCapabilities.spec.ts 
b/traffic_portal/test/integration/specs/ServerServerCapabilities.spec.ts
index c3efb2f..d21c6d9 100644
--- a/traffic_portal/test/integration/specs/ServerServerCapabilities.spec.ts
+++ b/traffic_portal/test/integration/specs/ServerServerCapabilities.spec.ts
@@ -22,10 +22,9 @@ import { LoginPage } from '../PageObjects/LoginPage.po'
 import { ServerCapabilitiesPage } from 
'../PageObjects/ServerCapabilitiesPage.po';
 import { TopNavigationPage } from '../PageObjects/TopNavigationPage.po';
 import { ServersPage } from '../PageObjects/ServersPage.po';
-import { API } from '../CommonUtils/API';
+import { api } from "../config";
 import { serverServerCapabilities } from "../Data";
 
-const api = new API();
 const loginPage = new LoginPage();
 const topNavigation = new TopNavigationPage();
 const serverCapabilitiesPage = new ServerCapabilitiesPage();
diff --git a/traffic_portal/test/integration/specs/Servers.spec.ts 
b/traffic_portal/test/integration/specs/Servers.spec.ts
index 536a166..867397e 100644
--- a/traffic_portal/test/integration/specs/Servers.spec.ts
+++ b/traffic_portal/test/integration/specs/Servers.spec.ts
@@ -20,11 +20,10 @@ import { browser } from 'protractor';
 
 import { LoginPage } from '../PageObjects/LoginPage.po'
 import { ServersPage } from '../PageObjects/ServersPage.po';
-import { API } from '../CommonUtils/API';
+import { api } from "../config";
 import { TopNavigationPage } from '../PageObjects/TopNavigationPage.po';
 import { servers } from "../Data";
 
-const api = new API();
 const loginPage = new LoginPage();
 const topNavigation = new TopNavigationPage();
 const serversPage = new ServersPage();
diff --git a/traffic_portal/test/integration/specs/ServiceCategories.spec.ts 
b/traffic_portal/test/integration/specs/ServiceCategories.spec.ts
index 131658d..fdbcd6b 100644
--- a/traffic_portal/test/integration/specs/ServiceCategories.spec.ts
+++ b/traffic_portal/test/integration/specs/ServiceCategories.spec.ts
@@ -20,11 +20,10 @@ import { browser } from 'protractor';
 
 import { LoginPage } from '../PageObjects/LoginPage.po';
 import { TopNavigationPage } from '../PageObjects/TopNavigationPage.po';
-import { API } from '../CommonUtils/API';
+import { api } from "../config";
 import { ServiceCategoriesPage } from '../PageObjects/ServiceCategories.po';
 import { serviceCategories } from "../Data";
 
-const api = new API();
 const loginPage = new LoginPage();
 const topNavigation = new TopNavigationPage();
 const serviceCategoriesPage = new ServiceCategoriesPage();
diff --git a/traffic_portal/test/integration/specs/Statuses.spec.ts 
b/traffic_portal/test/integration/specs/Statuses.spec.ts
index bb3ac1b..5f070c3 100644
--- a/traffic_portal/test/integration/specs/Statuses.spec.ts
+++ b/traffic_portal/test/integration/specs/Statuses.spec.ts
@@ -20,11 +20,10 @@ import { browser } from 'protractor';
 
 import { LoginPage } from '../PageObjects/LoginPage.po';
 import { TopNavigationPage } from '../PageObjects/TopNavigationPage.po';
-import { API } from '../CommonUtils/API';
+import { api } from "../config";
 import { StatusesPage } from '../PageObjects/Statuses.po'
 import { statuses } from "../Data";
 
-const api = new API();
 const loginPage = new LoginPage();
 const topNavigation = new TopNavigationPage();
 const statusesPage = new StatusesPage();
diff --git a/traffic_portal/test/integration/specs/Topologies.spec.ts 
b/traffic_portal/test/integration/specs/Topologies.spec.ts
index b9d9ccb..89678ab 100644
--- a/traffic_portal/test/integration/specs/Topologies.spec.ts
+++ b/traffic_portal/test/integration/specs/Topologies.spec.ts
@@ -20,11 +20,10 @@
 import { browser } from 'protractor';
 import { LoginPage } from '../PageObjects/LoginPage.po';
 import { TopNavigationPage } from '../PageObjects/TopNavigationPage.po';
-import { API } from '../CommonUtils/API';
+import { api } from "../config";
 import { TopologiesPage } from '../PageObjects/TopologiesPage.po';
 import { topologies } from "../Data/topologies";
 
-const api = new API();
 const loginPage = new LoginPage();
 const topologiesPage = new TopologiesPage();
 const topNavigation = new TopNavigationPage();
@@ -56,7 +55,7 @@ topologies.tests.forEach(async  topologiesData =>{
             it('can logout', async function(){
                 expect(await topNavigation.Logout()).toBeTruthy();
             })
-        
+
         })
     })
 })
@@ -66,4 +65,3 @@ describe('Clean Up API for Topologies Test', () => {
         await api.UseAPI(topologies.cleanup);
     });
 });
-
diff --git a/traffic_portal/test/integration/specs/Types.spec.ts 
b/traffic_portal/test/integration/specs/Types.spec.ts
index 580635d..b9fb5d6 100644
--- a/traffic_portal/test/integration/specs/Types.spec.ts
+++ b/traffic_portal/test/integration/specs/Types.spec.ts
@@ -20,11 +20,10 @@ import { browser } from 'protractor';
 
 import { LoginPage } from '../PageObjects/LoginPage.po';
 import { TopNavigationPage } from '../PageObjects/TopNavigationPage.po';
-import { API } from '../CommonUtils/API';
+import { api } from "../config";
 import { TypesPage } from '../PageObjects/Types.po'
 import { types } from "../Data";
 
-const api = new API();
 const loginPage = new LoginPage();
 const topNavigation = new TopNavigationPage();
 const typesPage = new TypesPage();

Reply via email to