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

shamrick 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 96b8efb03f Add e2e tests for TPv2 Servers table (#6773)
96b8efb03f is described below

commit 96b8efb03f4e69566785ebc95ef5b798718c8317
Author: ocket8888 <[email protected]>
AuthorDate: Mon May 2 15:18:59 2022 -0600

    Add e2e tests for TPv2 Servers table (#6773)
    
    * Fix ESLint not properly detecting valid uses of `this`
    
    * Add global definition of a test suite type
    
    * Add generic table methods
    
    * Add servers table page object and tests
    
    * Remove unused function
    
    * Fix incorrect servers page selector
    
    * Add filtering test assertion
    
    * Simplify users page object and tests with new typings
    
    * Simplify login tests with new typings
    
    * Stop cachebusting in all tpv2 workflows
    
    * Move running steps back to action
    
    * Set window size explicitly
    
    * Fix clicking at the wrong time
    
    * Add pause to servers table
    
    * Fix test that couldn't possibly work
    
    * Fix import order
    
    * Rename workflow
    
    * Remove unused dependency
    
    * Upload JUNIT output
    
    * Move configuration change to configuration file
    
    * Skip npm install when cache is valid
    
    * Use npm clean install
---
 .github/actions/tpv2-integration-tests/README.md   |  16 --
 .github/actions/tpv2-integration-tests/action.yml  |  19 ---
 .github/actions/tpv2-integration-tests/cdn.json    |  10 +-
 .../actions/tpv2-integration-tests/entrypoint.sh   |  97 ++---------
 .github/workflows/tpv2.integration.tests.yml       | 115 -------------
 .github/workflows/tpv2.yml                         | 178 ++++++++++++++++++++-
 experimental/traffic-portal/.eslintrc.json         |  13 +-
 .../traffic-portal/nightwatch/globals/globals.ts   |  10 +-
 .../nightwatch/globals/{globals.ts => index.ts}    |  26 +--
 .../nightwatch/globals/tables/index.ts             |  99 ++++++++++++
 .../traffic-portal/nightwatch/nightwatch.conf.js   |   3 +-
 .../nightwatch/page_objects/servers.ts             |  55 +++++++
 .../nightwatch/page_objects/users.ts               |  36 +----
 .../traffic-portal/nightwatch/tests/login.spec.ts  |  19 ++-
 .../nightwatch/tests/servers.spec.ts               |  34 ++++
 .../traffic-portal/nightwatch/tests/users.spec.ts  |  25 +--
 16 files changed, 415 insertions(+), 340 deletions(-)

diff --git a/.github/actions/tpv2-integration-tests/README.md 
b/.github/actions/tpv2-integration-tests/README.md
index 3df1b87c57..2575893da1 100644
--- a/.github/actions/tpv2-integration-tests/README.md
+++ b/.github/actions/tpv2-integration-tests/README.md
@@ -19,22 +19,9 @@
 
 # tp-integration-tests javascript action
 this action runs the traffic portal integration tests
-- requires an smtp service (see `smtp_address` input)
 
 ## inputs
 
-### `smtp_address`
-**required** the address of an smtp server for use by traffic ops.
-
-### `smtp_port`
-**required** the address of an smtp server for use by traffic ops. required 
but defaults to `25`.
-
-### `smtp_user`
-**optional** the user to authenticate with for the smtp server.
-
-### `smtp_password`
-**optional** the password to authenticate with for the smtp server.
-
 ## outputs
 
 ### `exit-code`
@@ -90,7 +77,4 @@ jobs:
         uses: ./.github/actions/todb-init
       - name: Run TP
         uses: ./.github/actions/tpv2-integration-tests
-        with:
-          smtp_address: 172.17.0.1
-
 ```
diff --git a/.github/actions/tpv2-integration-tests/action.yml 
b/.github/actions/tpv2-integration-tests/action.yml
index 67d2ccc669..043a3317a0 100644
--- a/.github/actions/tpv2-integration-tests/action.yml
+++ b/.github/actions/tpv2-integration-tests/action.yml
@@ -17,27 +17,8 @@
 
 name: 'tpv2-integration-tests'
 description: 'Runs Traffic Portalv2 Integration tests'
-inputs:
-  smtp_address:
-    description: 'Address of an SMTP server to use for the Traffic Ops API 
tests'
-    required: true
-  smtp_port:
-    description: 'Port of an SMTP server to use for the Traffic Ops API tests'
-    required: true
-    default: '25'
-  smtp_user:
-    description: 'The user to authenticate with for the SMTP server.'
-    required: false
-  smtp_password:
-    description: 'The user to authenticate with for the SMTP server.'
-    required: false
 runs:
   using: composite
   steps:
     - run: ${{ github.action_path }}/entrypoint.sh
       shell: bash
-      env:
-        INPUT_SMTP_USER: ${{ inputs.smtp_user }}
-        INPUT_SMTP_PORT: ${{ inputs.smtp_port }}
-        INPUT_SMTP_ADDRESS: ${{ inputs.smtp_address }}
-        INPUT_SMTP_PASSWORD: ${{ inputs.smtp_password }}
diff --git a/.github/actions/tpv2-integration-tests/cdn.json 
b/.github/actions/tpv2-integration-tests/cdn.json
index fa6d2895a9..c4f60a624f 100644
--- a/.github/actions/tpv2-integration-tests/cdn.json
+++ b/.github/actions/tpv2-integration-tests/cdn.json
@@ -1,7 +1,7 @@
 {
        "hypnotoad": {
                "listen": [
-                       
"https://not-a-real-host.test:1?cert=$PWD/localhost.crt&key=$PWD/localhost.key&verify=0x00&ciphers=AES128-GCM-SHA256:HIGH:!RC4:!MD5:!aNULL:!EDH:!ED";
+                       
"https://not-a-real-host.test:1?cert=$GITHUB_WORKSPACE/traffic_ops/traffic_ops_golang/localhost.crt&key=$GITHUB_WORKSPACE/traffic_ops/traffic_ops_golang/localhost.key&verify=0x00&ciphers=AES128-GCM-SHA256:HIGH:!RC4:!MD5:!aNULL:!EDH:!ED";
                ],
                "user": "trafops",
                "group": "trafops",
@@ -35,7 +35,7 @@
                        "max_connections": 500,
                        "max_idle_connections": 30,
                        "query_timeout_seconds": 10,
-                       "aes_key_location": "/aes.key"
+                       "aes_key_location": "$GITHUB_WORKSPACE/aes.key"
                },
                "supported_ds_metrics": [ "kbps", "tps_total", "tps_2xx", 
"tps_3xx", "tps_4xx", "tps_5xx" ]
        },
@@ -59,9 +59,9 @@
        "inactivity_timeout": 60,
        "smtp": {
                "enabled": true,
-               "user": "$INPUT_SMTP_USER",
-               "password": "$INPUT_SMTP_PASSWORD",
-               "address": "${INPUT_SMTP_ADDRESS}:${INPUT_SMTP_PORT}"
+               "user": "",
+               "password": "",
+               "address": "172.17.0.1:25"
        },
        "InfluxEnabled": false
 }
diff --git a/.github/actions/tpv2-integration-tests/entrypoint.sh 
b/.github/actions/tpv2-integration-tests/entrypoint.sh
index 2765ca400e..b7f03aa004 100755
--- a/.github/actions/tpv2-integration-tests/entrypoint.sh
+++ b/.github/actions/tpv2-integration-tests/entrypoint.sh
@@ -16,97 +16,22 @@
 # specific language governing permissions and limitations
 # under the License.
 
-onFail() {
-  echo "Error on line ${1} of ${2}" >&2;
-  cd "${REPO_DIR}/experimental/traffic-portal"
-  if ! [[ -d Reports ]]; then
-    mkdir Reports;
-  fi
-  if [[ -d nightwatch/junit ]]; then
-    mv nightwatch/junit Reports
-  fi
-  if [[ -d nightwatch/screens ]]; then
-    mv nightwatch/screens Reports
-  fi
-  if [[ -d logs ]]; then
-    mv logs Reports
-  fi
-  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
-}
+set -ex
 
-trap 'onFail "${LINENO}" "${0}"' ERR
-set -o errexit -o nounset -o pipefail
-
-to_fqdn="https://localhost:6443";
-tp_fqdn="http://localhost:4200";
-
-export PGUSER="traffic_ops"
-export PGPASSWORD="twelve"
-export PGHOST="localhost"
-export PGDATABASE="traffic_ops"
-export PGPORT="5432"
-
-to_admin_username="admin"
-to_admin_password="twelve12"
-password_hash="$(<<PYTHON_COMMANDS 
PYTHONPATH="${GITHUB_WORKSPACE}/traffic_ops/install/bin" python
-import _postinstall
-print(_postinstall.hash_pass('${to_admin_password}'))
-PYTHON_COMMANDS
-)"
-<<QUERY psql
-INSERT INTO tm_user (username, role, tenant_id, local_passwd)
-  VALUES ('${to_admin_username}', 1, 1,
-    '${password_hash}'
-  );
-QUERY
-
-sudo useradd trafops
-
-ciab_dir="${GITHUB_WORKSPACE}/infrastructure/cdn-in-a-box";
-openssl rand 32 | base64 | sudo tee /aes.key
-
-sudo apt-get install -y --no-install-recommends gettext curl
-
-export GOPATH="${HOME}/go"
-readonly ORG_DIR="$GOPATH/src/github.com/apache"
-readonly REPO_DIR="${ORG_DIR}/trafficcontrol"
-resources="$(dirname "$0")"
-if [[ ! -e "$REPO_DIR" ]]; then
-       mkdir -p "$ORG_DIR"
-       cd
-       mv "${GITHUB_WORKSPACE}" "${REPO_DIR}/"
-       ln -s "$REPO_DIR" "${GITHUB_WORKSPACE}"
-fi
-
-pushd "${REPO_DIR}/traffic_ops/traffic_ops_golang"
-if  [[ ! -d "${GITHUB_WORKSPACE}/vendor/golang.org" ]]; then
-  go mod vendor
-fi
-go build .
-
-openssl req -new -x509 -nodes -newkey rsa:4096 -out localhost.crt -keyout 
localhost.key -subj "/CN=tptests";
-
-envsubst <"${resources}/cdn.json" >cdn.conf
-cp "${resources}/database.json" database.conf
+cd "${GITHUB_WORKSPACE}/traffic_ops/traffic_ops_golang"
 
 truncate -s0 out.log
-./traffic_ops_golang --cfg ./cdn.conf --dbcfg ./database.conf >out.log 2>&1 &
-popd
+envsubst <../../.github/actions/tpv2-integration-tests/cdn.json >./cdn.conf
+
+./traffic_ops_golang --cfg ./cdn.conf --dbcfg 
../../.github/actions/tpv2-integration-tests/database.json > out.log 2>&1 &
 
-cd "${REPO_DIR}/experimental/traffic-portal"
-npm ci
+cd "${GITHUB_WORKSPACE}/experimental/traffic-portal"
 npx ng serve &
 
-# Wait for tp/to build
 timeout 15m bash <<TMOUT
-  while ! curl -Lvsk "${tp_fqdn}/api/4.0/ping" >/dev/null 2>&1; do
-    echo "waiting for TP/TO server to start on '${tp_fqdn}'"
-    sleep 30
-  done
+       while ! curl -k "http://localhost:4200/api/4.0/ping"; >/dev/null 2>&1; do
+               echo "waiting for TP dev server to proxy TO API"
+               sleep 5
+       done
 TMOUT
-
-npm run e2e:ci
+timeout 15m npm run e2e:ci
diff --git a/.github/workflows/tpv2.integration.tests.yml 
b/.github/workflows/tpv2.integration.tests.yml
deleted file mode 100644
index 19c367e7ad..0000000000
--- a/.github/workflows/tpv2.integration.tests.yml
+++ /dev/null
@@ -1,115 +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.
-
-name: TPv2 Integration Tests
-
-env:
-  # alpine:3.13
-  ALPINE_VERSION: 
sha256:08d6ca16c60fe7490c03d10dc339d9fd8ea67c6466dea8d558526b1330a85930
-
-on:
-  push:
-    paths:
-      - .github/actions/tpv2-integration-tests/**
-      - .github/workflows/tpv2.integration.tests.yml
-      - experimental/traffic-portal/**
-      # Uncomment these when TPv2 is no longer experimental
-      # - .github/actions/todb-init/**
-      # - .github/actions/tvdb-init/**
-      # - GO_VERSION
-      # - infrastructure/cdn-in-a-box/optional/traffic_vault/**
-      # - traffic_ops/*client/**.go
-      # - traffic_ops/testing/api/**.go
-      #- traffic_ops/traffic_ops_golang/**.go
-  create:
-  pull_request:
-    paths:
-      - .github/actions/tpv2-integration-tests/**
-      - .github/workflows/tpv2.integration.tests.yml
-      - experimental/traffic-portal/**
-    types: [ opened, reopened, ready_for_review, synchronize ]
-
-jobs:
-  TP_Integration_tests:
-    if: github.event.pull_request.draft == false
-    runs-on: ubuntu-latest
-    services:
-      postgres:
-        image: postgres:11
-        env:
-          POSTGRES_USER: traffic_ops
-          POSTGRES_PASSWORD: twelve
-          POSTGRES_DB: traffic_ops
-        ports:
-          - 5432:5432
-        options: --health-cmd pg_isready --health-interval 10s 
--health-timeout 5s --health-retries 5
-      smtp:
-        image: maildev/maildev:2.0.0-beta3
-        ports:
-          - 25:25
-        options: >-
-          --entrypoint=bin/maildev
-          --user=root
-          --health-cmd="sh -c \"[[ \$(wget -qO- http://smtp/healthz) == true 
]]\""
-          --
-          maildev/maildev:2.0.0-beta3
-          --smtp=25
-          --hide-extensions=STARTTLS
-          --web=80
-
-    steps:
-      - name: Checkout
-        uses: actions/checkout@master
-      - name: Cache Alpine Docker image
-        uses: actions/cache@v2
-        with:
-          path: ${{ github.workspace }}/docker-images
-          key: docker-images/alpine@${{ env.ALPINE_VERSION }}.tar.gz
-      - name: Import cached Alpine Docker image
-        run: .github/actions/save-alpine-tar/entrypoint.sh load ${{ 
env.ALPINE_VERSION }}
-      - name: Cache node modules
-        uses: actions/cache@v2
-        with:
-          path: ./experimental/traffic-portal/node_modules
-          key: ${{ runner.os }}-node-${{ 
hashFiles('./experimental/traffic-portal/package-lock.json') }}
-          restore-keys: |
-            ${{ runner.os }}-node-modules-
-      - name: Initialize Traffic Ops Database
-        id: todb
-        uses: ./.github/actions/todb-init
-      - name: Initialize Traffic Vault Database
-        id: tvdb
-        uses: ./.github/actions/tvdb-init
-      - name: Check Go Version
-        run: echo "::set-output name=value::$(cat GO_VERSION)"
-        id: go-version
-      - name: Install Go
-        uses: actions/setup-go@v2
-        with:
-          go-version: ${{ steps.go-version.outputs.value }}
-      - name: Run TP
-        uses: ./.github/actions/tpv2-integration-tests
-        with:
-          smtp_address: 172.17.0.1
-      - name: Upload Report
-        uses: actions/upload-artifact@v2
-        if: always()
-        with:
-          name: ${{ github.job }}
-          path: ${{ github.workspace }}/expirimental/traffic-portal/Reports
-      - name: Save Alpine Docker image
-        run: .github/actions/save-alpine-tar/entrypoint.sh save ${{ 
env.ALPINE_VERSION }}
diff --git a/.github/workflows/tpv2.yml b/.github/workflows/tpv2.yml
index 70e93a450b..066f55a944 100644
--- a/.github/workflows/tpv2.yml
+++ b/.github/workflows/tpv2.yml
@@ -14,25 +14,32 @@
 # KIND, either express or implied.  See the License for the
 # specific language governing permissions and limitations
 # under the License.
-name: Lint and Test Experimental Traffic Portal
+name: Experimental Traffic Portal v2
+
+env:
+  # alpine:3.13
+  ALPINE_VERSION: 
sha256:08d6ca16c60fe7490c03d10dc339d9fd8ea67c6466dea8d558526b1330a85930
 
 on:
   pull_request:
     paths:
       - experimental/traffic-portal/**
       - .github/workflows/tpv2.yml
-    types: [opened, reopened, edited, synchronize]
+      - .github/actions/tpv2-integration-tests
+    types: [opened, reopened, ready_for_review, synchronize]
 
 jobs:
   build:
+    if: github.event.pull_request.draft == false
     runs-on: ubuntu-latest
 
     steps:
       - name: Checkout
-        uses: actions/checkout@v1
+        uses: actions/checkout@v3
 
       - name: Cache node modules
-        uses: actions/cache@v1
+        id: restore-npm-cache
+        uses: actions/cache@v3
         with:
           path: ./experimental/traffic-portal/node_modules
           key: ${{ runner.os }}-node-${{ 
hashFiles('./experimental/traffic-portal/package-lock.json') }}
@@ -40,14 +47,12 @@ jobs:
             ${{ runner.os }}-node-
 
       - name: Node 16
-        uses: actions/setup-node@v1
+        uses: actions/setup-node@v3
         with:
           node-version: 16.x
 
-      - name: Install latest Chrome
-        run: sudo apt-get update && sudo apt-get install google-chrome-stable
-
       - name: NPM install
+        if: steps.restore-npm-cache.cache-hit != 'true'
         run: |
           cd experimental/traffic-portal/
           npm ci
@@ -56,13 +61,170 @@ jobs:
         run: |
           cd experimental/traffic-portal/
           npm run build:ssr
+  lint:
+    if: github.event.pull_request.draft == false
+    runs-on: ubuntu-latest
+
+    steps:
+      - name: Checkout
+        uses: actions/checkout@v3
+
+      - name: Cache node modules
+        id: restore-npm-cache
+        uses: actions/cache@v3
+        with:
+          path: ./experimental/traffic-portal/node_modules
+          key: ${{ runner.os }}-node-${{ 
hashFiles('./experimental/traffic-portal/package-lock.json') }}
+          restore-keys: |
+            ${{ runner.os }}-node-
+
+      - name: Node 16
+        uses: actions/setup-node@v3
+        with:
+          node-version: 16.x
+
+      - name: NPM install
+        if: steps.restore-npm-cache.cache-hit != 'true'
+        run: |
+          cd experimental/traffic-portal/
+          npm ci
 
       - name: Lint
         run: |
           cd experimental/traffic-portal/
           npm run lint
+  unit-tests:
+    if: github.event.pull_request.draft == false
+    runs-on: ubuntu-latest
+
+    steps:
+      - name: Checkout
+        uses: actions/checkout@v3
+
+      - name: Cache node modules
+        id: restore-npm-cache
+        uses: actions/cache@v3
+        with:
+          path: ./experimental/traffic-portal/node_modules
+          key: ${{ runner.os }}-node-${{ 
hashFiles('./experimental/traffic-portal/package-lock.json') }}
+          restore-keys: |
+            ${{ runner.os }}-node-
+
+      - name: Node 16
+        uses: actions/setup-node@v3
+        with:
+          node-version: 16.x
+
+      - name: Install latest Chrome
+        run: sudo apt-get update && sudo apt-get install google-chrome-stable
+
+      - name: NPM install
+        if: steps.restore-npm-cache.cache-hit != 'true'
+        run: |
+          cd experimental/traffic-portal/
+          npm ci
 
       - name: Test
         run: |
           cd experimental/traffic-portal/
           npm run test:ci
+  end-to-end-tests:
+    if: github.event.pull_request.draft == false
+    runs-on: ubuntu-latest
+    env:
+      PGUSER: traffic_ops
+      PGPASSWORD: twelve
+      PGHOST: localhost
+      PGDATABASE: traffic_ops
+      PGPORT: 5432
+    services:
+      postgres:
+        image: postgres:13
+        env:
+          POSTGRES_USER: traffic_ops
+          POSTGRES_PASSWORD: twelve
+          POSTGRES_DB: traffic_ops
+        ports:
+          - 5432:5432
+        options: --health-cmd pg_isready --health-interval 10s 
--health-timeout 5s --health-retries 5
+      smtp:
+        image: maildev/maildev:2.0.0-beta3
+        ports:
+          - 25:25
+        options: >-
+          --entrypoint=bin/maildev
+          --user=root
+          --health-cmd="sh -c \"[[ \$(wget -qO- http://smtp/healthz) == true 
]]\""
+          --
+          maildev/maildev:2.0.0-beta3
+          --smtp=25
+          --hide-extensions=STARTTLS
+          --web=80
+
+    steps:
+      - name: Checkout
+        uses: actions/checkout@v3
+      - name: Cache Alpine Docker image
+        uses: actions/cache@v3
+        with:
+          path: ${{ github.workspace }}/docker-images
+          key: docker-images/alpine@${{ env.ALPINE_VERSION }}.tar.gz
+      - name: Import cached Alpine Docker image
+        run: .github/actions/save-alpine-tar/entrypoint.sh load ${{ 
env.ALPINE_VERSION }}
+      - name: Cache node modules
+        id: restore-npm-cache
+        uses: actions/cache@v3
+        with:
+          path: ./experimental/traffic-portal/node_modules
+          key: ${{ runner.os }}-node-${{ 
hashFiles('./experimental/traffic-portal/package-lock.json') }}
+          restore-keys: |
+            ${{ runner.os }}-node-modules-
+      - name: Initialize Traffic Ops Database
+        id: todb
+        uses: ./.github/actions/todb-init
+      - name: Initialize Traffic Vault Database
+        id: tvdb
+        uses: ./.github/actions/tvdb-init
+      - name: Check Go Version
+        run: echo "::set-output name=value::$(cat GO_VERSION)"
+        id: go-version
+      - name: Install Go
+        uses: actions/setup-go@v3
+        with:
+          go-version: ${{ steps.go-version.outputs.value }}
+      - name: Build Traffic Ops
+        run: |
+          cd "${GITHUB_WORKSPACE}/traffic_ops/traffic_ops_golang"
+          go build .
+
+      # Setup
+      - name: Install dependencies
+        run: sudo apt-get update && sudo apt-get install postgresql-client 
gettext-base
+      - name: Create admin user
+        run: |
+          psql -c "INSERT INTO tm_user (username, role, tenant_id, 
local_passwd) VALUES ('admin', 1, 1, 
'SCRYPT:16384:8:1:p0Bppp/6IBeYxSwdLuYddsdMLBU/BNSlLY6fSIF7H1XW4eTbNVeMPVm7TuTEG4FM8PbqLlVwi8sPy8ZJznAlaQ==:sRcHWGe43mm/uEmXTIw37GcLEQZTlWAdf4vJqK8f0MDh8P+8gXoNx+nxWyb3r/0Bh+yyg0g/dUvti/ePZJL+Jw==');"
+      - name: Create SSL Certificates and AES key
+        run: |
+          openssl rand 32 | base64 | tee "${GITHUB_WORKSPACE}/aes.key"
+          openssl req -new -x509 -nodes -newkey rsa:4096 -out 
traffic_ops/traffic_ops_golang/localhost.crt -keyout 
traffic_ops/traffic_ops_golang/localhost.key -subj "/CN=tptests"
+      - name: NPM install
+        if: steps.restore-npm-cache.cache-hit != 'true'
+        run: |
+          cd experimental/traffic-portal
+          npm ci
+      - name: Run everything and test
+        uses: ./.github/actions/tpv2-integration-tests
+      - name: Upload Report
+        uses: actions/upload-artifact@v3
+        if: always()
+        with:
+          name: ${{ github.job }}
+          path: |
+            traffic_ops/traffic_ops_golang/out.log
+            experimental/traffic-portal/logs
+            experimental/traffic-portal/nightwatch/junit
+            experimental/traffic-portal/nightwatch/screens
+            experimental/traffic-portal/tests_output
+
+      - name: Save Alpine Docker image
+        run: .github/actions/save-alpine-tar/entrypoint.sh save ${{ 
env.ALPINE_VERSION }}
diff --git a/experimental/traffic-portal/.eslintrc.json 
b/experimental/traffic-portal/.eslintrc.json
index 9e49372860..8ff5ffa0c1 100644
--- a/experimental/traffic-portal/.eslintrc.json
+++ b/experimental/traffic-portal/.eslintrc.json
@@ -177,6 +177,12 @@
                                                "ignoreParameters": true
                                        }
                                ],
+                               "@typescript-eslint/no-invalid-this": [
+                                       "error",
+                                       {
+                                               "capIsConstructor": false
+                                       }
+                               ],
                                "@typescript-eslint/no-invalid-void-type": 
"error",
                                "@typescript-eslint/no-misused-new": "error",
                                "@typescript-eslint/no-misused-promises": 
"error",
@@ -308,12 +314,7 @@
                                "no-else-return": "error",
                                "no-empty": "error",
                                "no-extra-bind": "error",
-                               "no-invalid-this": [
-                                       "error",
-                                       {
-                                               "capIsConstructor": false
-                                       }
-                               ],
+                               "no-invalid-this": "off",
                                "no-multiple-empty-lines": [
                                        "error",
                                        {
diff --git a/experimental/traffic-portal/nightwatch/globals/globals.ts 
b/experimental/traffic-portal/nightwatch/globals/globals.ts
index d1ed9e379a..a624e86707 100644
--- a/experimental/traffic-portal/nightwatch/globals/globals.ts
+++ b/experimental/traffic-portal/nightwatch/globals/globals.ts
@@ -23,13 +23,9 @@ export interface GlobalConfig extends NightwatchGlobals {
        trafficOpsURL: string;
 }
 const config = {
-       chrome_headless: {},
-       default: {
-               adminPass: "twelve12",
-               adminUser: "admin",
-               trafficOpsURL: "https://localhost:6443";
-       }
+       adminPass: "twelve12",
+       adminUser: "admin",
+       trafficOpsURL: "https://localhost:6443";
 };
-config.chrome_headless = config.default;
 
 module.exports = config;
diff --git a/experimental/traffic-portal/nightwatch/globals/globals.ts 
b/experimental/traffic-portal/nightwatch/globals/index.ts
similarity index 55%
copy from experimental/traffic-portal/nightwatch/globals/globals.ts
copy to experimental/traffic-portal/nightwatch/globals/index.ts
index d1ed9e379a..cf939cab87 100644
--- a/experimental/traffic-portal/nightwatch/globals/globals.ts
+++ b/experimental/traffic-portal/nightwatch/globals/index.ts
@@ -1,5 +1,4 @@
 /*
-*
 * Licensed 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
@@ -12,24 +11,15 @@
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */
-import {type NightwatchGlobals} from "nightwatch";
+
+import type { NightwatchBrowser } from "nightwatch";
+
+import type { GlobalConfig } from "./globals";
 
 /**
- * Defines the configuration used for the testing environment
+ * A test suite is a mapping of test descriptions to the functions that
+ * implement the thereby described test.
  */
-export interface GlobalConfig extends NightwatchGlobals {
-       adminPass: string;
-       adminUser: string;
-       trafficOpsURL: string;
+export interface TestSuite {
+       [description: string]: (browser: NightwatchBrowser & {globals: 
GlobalConfig}) => (void | Promise<void>);
 }
-const config = {
-       chrome_headless: {},
-       default: {
-               adminPass: "twelve12",
-               adminUser: "admin",
-               trafficOpsURL: "https://localhost:6443";
-       }
-};
-config.chrome_headless = config.default;
-
-module.exports = config;
diff --git a/experimental/traffic-portal/nightwatch/globals/tables/index.ts 
b/experimental/traffic-portal/nightwatch/globals/tables/index.ts
new file mode 100644
index 0000000000..d48af8f7b9
--- /dev/null
+++ b/experimental/traffic-portal/nightwatch/globals/tables/index.ts
@@ -0,0 +1,99 @@
+/*
+* Licensed 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 type { EnhancedElementInstance, EnhancedPageObject, 
EnhancedSectionInstance } from "nightwatch";
+
+/**
+ * TableSectionCommands is the base type for page object sections representing
+ * pages containing AG-Grid generic tables.
+ */
+export interface TableSectionCommands extends EnhancedSectionInstance, 
EnhancedElementInstance<EnhancedPageObject> {
+       getColumnState(column: string): Promise<boolean>;
+       searchText<T extends this>(text: string): T;
+       toggleColumn<T extends this>(column: string): T;
+}
+
+/**
+ * A CSS selector for an AG-Grid generic table's column visibility dropdown
+ * menu.
+ */
+export const columnMenuSelector = "button.dropdown-toggle";
+
+/**
+ * A CSS selector for an AG-Grid generic table's "Fuzzy Search" input text box.
+ */
+export const searchboxSelector = "input[name='fuzzControl']";
+
+/**
+ * Gets the state of an AG-Grid column by checking whether or not it's checked
+ * in the column visibility menu (doesn't actually verify that this means the
+ * column is visible).
+ *
+ * @param this Special parameter that tells the compiler what `this` is in a
+ * valid context for this function.
+ * @param column The name of the column being retrieved.
+ * @returns The state of the column named `column`. Behavior is undefined if
+ * multiple columns exist with the same given name.
+ */
+export async function getColumnState(this: TableSectionCommands, column: 
string): Promise<boolean> {
+       return new Promise((resolve, reject) => {
+               
this.click(columnMenuSelector).getElementProperty(`input[name='column-${column}']`,
 "checked",
+                       result => {
+                               if (typeof result.value !== "boolean") {
+                                       console.error("incorrect type for 
'checked' DOM property:", result.value);
+                                       reject(new Error(`incorrect type for 
'checked' DOM property: ${typeof result.value}`));
+                                       return;
+                               }
+                               this.click(columnMenuSelector);
+                               resolve(result.value);
+                       }
+               );
+       });
+}
+
+/**
+ * Sets the text of the table's "Fuzzy Search" searchbox.
+ *
+ * @param this Special parameter that tells the compiler what `this` is in a
+ * valid context for this function.
+ * @param text The text to set in the search input.
+ * @returns The calling command section for call-chaining the way Nightwatch
+ * likes to do.
+ */
+export function searchText<T extends TableSectionCommands>(this: T, text: 
string): T  {
+       return this.setValue(searchboxSelector, text);
+}
+
+/**
+ * Toggles the presence of a given column.
+ *
+ * @param this Special parameter that tells the compiler what `this` is in a
+ * valid context for this function.
+ * @param column The name of the column to be toggled.
+ * @returns The calling command section for call-chaining the way Nightwatch
+ * likes to do.
+ */
+export function toggleColumn<T extends TableSectionCommands>(this: T, column: 
string): T {
+       return 
this.click(columnMenuSelector).click(`input[name='${column}']`).click(columnMenuSelector);
+}
+
+/**
+ * This is meant to be mixed-in to generic table page object command sections,
+ * to most easily provide all the functionality of a table.
+ */
+export const TABLE_COMMANDS = {
+       getColumnState,
+       searchText,
+       toggleColumn
+};
diff --git a/experimental/traffic-portal/nightwatch/nightwatch.conf.js 
b/experimental/traffic-portal/nightwatch/nightwatch.conf.js
index a17707d4c4..eb6cf1d1fe 100644
--- a/experimental/traffic-portal/nightwatch/nightwatch.conf.js
+++ b/experimental/traffic-portal/nightwatch/nightwatch.conf.js
@@ -62,7 +62,8 @@ module.exports = {
                        desiredCapabilities: {
                                "goog:chromeOptions": {
                                        args: [
-                                               "--headless"
+                                               "--headless",
+                                               "--window-size=1920,1080"
                                        ]
                                }
                        },
diff --git a/experimental/traffic-portal/nightwatch/page_objects/servers.ts 
b/experimental/traffic-portal/nightwatch/page_objects/servers.ts
new file mode 100644
index 0000000000..79e8bf5246
--- /dev/null
+++ b/experimental/traffic-portal/nightwatch/page_objects/servers.ts
@@ -0,0 +1,55 @@
+/*
+* Licensed 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 {
+       EnhancedPageObject,
+       EnhancedSectionInstance,
+       NightwatchAPI
+} from "nightwatch";
+
+import { TableSectionCommands, TABLE_COMMANDS } from "../globals/tables";
+
+/**
+ * Defines the commands for the servers table section.
+ */
+type ServersTableSectionCommands = TableSectionCommands;
+
+const serversPageObject = {
+       api: {} as NightwatchAPI,
+       sections: {
+               serversTable: {
+                       commands: {
+                               ...TABLE_COMMANDS
+                       } as ServersTableSectionCommands,
+                       elements: {
+                       },
+                       selector: "servers-table main"
+               }
+       },
+       url(): string {
+               return `${this.api.launchUrl}/core/servers`;
+       }
+};
+
+/**
+ * Defines the servers table section.
+ */
+type ServersTableSection = 
EnhancedSectionInstance<ServersTableSectionCommands, typeof 
serversPageObject.sections.serversTable.elements>;
+
+/**
+ * The type of the servers table page object as provided by the Nightwatch API 
at
+ * runtime.
+ */
+export type ServersPageObject = EnhancedPageObject<{}, {}, { serversTable: 
ServersTableSection }>;
+
+export default serversPageObject;
diff --git a/experimental/traffic-portal/nightwatch/page_objects/users.ts 
b/experimental/traffic-portal/nightwatch/page_objects/users.ts
index b5fd50e380..02ca591aaa 100644
--- a/experimental/traffic-portal/nightwatch/page_objects/users.ts
+++ b/experimental/traffic-portal/nightwatch/page_objects/users.ts
@@ -12,54 +12,26 @@
 * limitations under the License.
 */
 import {
-       EnhancedElementInstance,
        EnhancedPageObject,
        EnhancedSectionInstance,
        NightwatchAPI
 } from "nightwatch";
 
+import { TableSectionCommands, TABLE_COMMANDS } from "../globals/tables";
+
 /**
  * Defines the commands for the users table section.
  */
-interface UsersTableSectionCommands extends EnhancedSectionInstance, 
EnhancedElementInstance<EnhancedPageObject> {
-       getColumnState(column: string): Promise<boolean>;
-       searchText(text: string): this;
-       toggleColumn(column: string): this;
-}
+type UsersTableSectionCommands = TableSectionCommands;
 
 const usersPageObject = {
        api: {} as NightwatchAPI,
        sections: {
                usersTable: {
                        commands: {
-                               async getColumnState(column: string): 
Promise<boolean> {
-                                       return new Promise((resolve, reject) => 
{
-                                               
this.click("@columnMenu").getElementProperty(`input[name='column-${column}']`, 
"checked",
-                                                       result => {
-                                                               if (typeof 
result.value !== "boolean") {
-                                                                       
console.error("incorrect type for 'checked' DOM property:", result.value);
-                                                                       
reject(new Error(`incorrect type for 'checked' DOM property: ${typeof 
result.value}`));
-                                                                       return;
-                                                               }
-                                                               
resolve(result.value);
-                                                       }
-                                               ).click("@columnMenu");
-                                       });
-                               },
-                               searchText(text: string): 
UsersTableSectionCommands  {
-                                        return this.setValue("@searchbox", 
text);
-                               },
-                               toggleColumn(column: string): 
UsersTableSectionCommands {
-                                       return 
this.click("@columnMenu").click(`input[name='${column}']`).click("@columnMenu");
-                               },
+                               ...TABLE_COMMANDS
                        } as UsersTableSectionCommands,
                        elements: {
-                               columnMenu: {
-                                       selector: "button.dropdown-toggle"
-                               },
-                               searchbox: {
-                                       selector: "input[name='fuzzControl']"
-                               },
                        },
                        selector: "main > main"
                }
diff --git a/experimental/traffic-portal/nightwatch/tests/login.spec.ts 
b/experimental/traffic-portal/nightwatch/tests/login.spec.ts
index 0f8e928258..ec92912df0 100644
--- a/experimental/traffic-portal/nightwatch/tests/login.spec.ts
+++ b/experimental/traffic-portal/nightwatch/tests/login.spec.ts
@@ -11,13 +11,11 @@
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */
-import {type NightwatchBrowser} from "nightwatch";
+import type { TestSuite } from "../globals";
+import type { LoginPageObject } from "../page_objects/login";
 
-import {type GlobalConfig} from "../globals/globals";
-import {type LoginPageObject} from "../page_objects/login";
-
-module.exports = {
-       "Clear form test": (browser: NightwatchBrowser): void => {
+const suite: TestSuite = {
+       "Clear form test": browser => {
                const page: LoginPageObject = browser.page.login();
                page.navigate()
                        .section.loginForm
@@ -27,7 +25,7 @@ module.exports = {
                        .assert.containsText("@passwordTxt", "")
                        .end();
        },
-       "Incorrect password test":  (browser: NightwatchBrowser): void => {
+       "Incorrect password test":  browser => {
                const page: LoginPageObject = browser.page.login();
                page.navigate()
                        .section.loginForm
@@ -38,14 +36,15 @@ module.exports = {
                        .assert.containsText("@snackbarEle", "Invalid")
                        .end();
        },
-       "Login test": (browser: NightwatchBrowser): void => {
+       "Login test": browser => {
                const page: LoginPageObject = browser.page.login();
-               const globals = browser.globals as GlobalConfig;
                page.navigate()
                        .section.loginForm
-                       .login(globals.adminUser, globals.adminPass)
+                       .login(browser.globals.adminUser, 
browser.globals.adminPass)
                        .parent
                        .assert.containsText("@snackbarEle", "Success")
                        .end();
        }
 };
+
+export default suite;
diff --git a/experimental/traffic-portal/nightwatch/tests/servers.spec.ts 
b/experimental/traffic-portal/nightwatch/tests/servers.spec.ts
new file mode 100644
index 0000000000..301683b01e
--- /dev/null
+++ b/experimental/traffic-portal/nightwatch/tests/servers.spec.ts
@@ -0,0 +1,34 @@
+/*
+* Licensed 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 type { TestSuite } from "../globals";
+import type { LoginPageObject } from "../page_objects/login";
+import type { ServersPageObject } from "../page_objects/servers";
+
+const suite: TestSuite = {
+       "Filter by hostname": async browser => {
+               const username = browser.globals.adminUser;
+               const password = browser.globals.adminPass;
+
+               const loginPage: LoginPageObject = browser.page.login();
+               loginPage.navigate().section.loginForm.login(username, 
password);
+
+               const page: ServersPageObject = 
browser.waitForElementPresent("main").page.servers().navigate();
+               page.pause(4000);
+               let tbl = 
page.waitForElementPresent("input[name=fuzzControl]").section.serversTable;
+               tbl = tbl.searchText("edge");
+               tbl.parent.assert.urlContains("search=edge").end();
+       }
+};
+
+export default suite;
diff --git a/experimental/traffic-portal/nightwatch/tests/users.spec.ts 
b/experimental/traffic-portal/nightwatch/tests/users.spec.ts
index 31630f563c..a74ee7f00b 100644
--- a/experimental/traffic-portal/nightwatch/tests/users.spec.ts
+++ b/experimental/traffic-portal/nightwatch/tests/users.spec.ts
@@ -11,24 +11,14 @@
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */
-import type { NightwatchBrowser } from "nightwatch";
-import { LoginPageObject } from "nightwatch/page_objects/login";
-
-import type { GlobalConfig } from "../globals/globals";
+import type { TestSuite } from "../globals";
+import { LoginPageObject } from "../page_objects/login";
 import type { UsersPageObject } from "../page_objects/users";
 
-/**
- * A test suite is a mapping of test descriptions to the functions that
- * implement the thereby described test.
- */
-interface TestSuite {
-       [description: string]: (browser: NightwatchBrowser) => (void | 
Promise<void>);
-}
-
 const suite: TestSuite = {
        "Filter by username": async browser => {
-               const username = (browser.globals as GlobalConfig).adminUser;
-               const password = (browser.globals as GlobalConfig).adminPass;
+               const username = browser.globals.adminUser;
+               const password = browser.globals.adminPass;
 
                const loginPage: LoginPageObject = browser.page.login();
                loginPage.navigate().section.loginForm.login(username, 
password);
@@ -48,14 +38,15 @@ const suite: TestSuite = {
                                        browser.assert.equal(true, false, 
`failed to select ag-grid rows: ${result.value.message}`);
                                        return;
                                }
-                               browser.assert.equal(result.value.length, 1);
+                               browser.assert.equal(result.value.length, 1)
+                                       .end();
                        }
                );
        },
        // Uncomment when user details page exists
        // "View user details":  browser => {
-       //      const username = (browser.globals as GlobalConfig).adminUser;
-       //      const password = (browser.globals as GlobalConfig).adminPass;
+       //      const username = browser.globals.adminUser;
+       //      const password = browser.globals.adminPass;
 
        //      const loginPage: LoginPageObject = browser.page.login();
        //      loginPage.navigate().section.loginForm.login(username, 
password);

Reply via email to