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

ocket8888 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 866d7a169d Add Various E2E TPv2 DS Tests (#6805)
866d7a169d is described below

commit 866d7a169d9291c925446013a75f3d7f7c39c10b
Author: Steve Hamrick <[email protected]>
AuthorDate: Tue May 10 11:06:30 2022 -0600

    Add Various E2E TPv2 DS Tests (#6805)
    
    * Add DS e2e tests
    
    * Run without PR
    
    * Cleanup
    
    * Dont use silly ids, actually create the data before test runs
    
    * Lint fix
    
    * Code review changes
    
    * Allow tests to be ran multiple times
    
    * Dont define custom types, use module augmentation
    
    * Dont call login or end session in each suite
    
    * Wait for callback
    
    * Lint fix
---
 .github/workflows/tpv2.yml                         |   2 +
 .../traffic-portal/nightwatch/globals/globals.ts   | 158 +++++++++++++++++++--
 .../traffic-portal/nightwatch/globals/index.ts     |  25 ----
 .../traffic-portal/nightwatch/nightwatch.conf.js   |   2 +
 .../nightwatch/page_objects/common.ts              |  30 ++++
 .../nightwatch/page_objects/deliveryServiceCard.ts |  77 ++++++++++
 .../page_objects/deliveryServiceDetail.ts          |  60 ++++++++
 .../deliveryServiceInvalidationJobs.ts             |  32 +++++
 .../nightwatch/page_objects/login.ts               |  20 +--
 .../traffic-portal/nightwatch/tests/ds/ds.card.ts  |  29 ++++
 .../nightwatch/tests/ds/ds.details.spec.ts         |  51 +++++++
 .../nightwatch/tests/ds/ds.invalidate.spec.ts      |  63 ++++++++
 .../traffic-portal/nightwatch/tests/login.spec.ts  |  50 -------
 .../nightwatch/tests/login/login.spec.ts           |  40 ++++++
 .../nightwatch/tests/servers.spec.ts               |  34 -----
 .../nightwatch/tests/servers/servers.spec.ts       |  37 +++++
 .../nightwatch/tests/{ => users}/users.spec.ts     |  27 ++--
 .../traffic-portal/nightwatch/tsconfig.e2e.json    |   3 +-
 experimental/traffic-portal/package-lock.json      |  65 +++++++--
 experimental/traffic-portal/package.json           |   4 +
 .../deliveryservice/deliveryservice.component.html |   6 +-
 .../src/app/core/ds-card/ds-card.component.html    |   2 +-
 .../invalidation-jobs.component.html               |   2 +-
 23 files changed, 659 insertions(+), 160 deletions(-)

diff --git a/.github/workflows/tpv2.yml b/.github/workflows/tpv2.yml
index 066f55a944..7968059e98 100644
--- a/.github/workflows/tpv2.yml
+++ b/.github/workflows/tpv2.yml
@@ -21,6 +21,8 @@ env:
   ALPINE_VERSION: 
sha256:08d6ca16c60fe7490c03d10dc339d9fd8ea67c6466dea8d558526b1330a85930
 
 on:
+  push:
+  create:
   pull_request:
     paths:
       - experimental/traffic-portal/**
diff --git a/experimental/traffic-portal/nightwatch/globals/globals.ts 
b/experimental/traffic-portal/nightwatch/globals/globals.ts
index a624e86707..e6d4691e33 100644
--- a/experimental/traffic-portal/nightwatch/globals/globals.ts
+++ b/experimental/traffic-portal/nightwatch/globals/globals.ts
@@ -12,20 +12,156 @@
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */
-import {type NightwatchGlobals} from "nightwatch";
+import * as https from "https";
 
-/**
- * Defines the configuration used for the testing environment
- */
-export interface GlobalConfig extends NightwatchGlobals {
-       adminPass: string;
-       adminUser: string;
-       trafficOpsURL: string;
+import axios, {AxiosError} from "axios";
+import {NightwatchBrowser} from "nightwatch";
+import type {CommonPageObject} from "nightwatch/page_objects/common";
+import type {DeliveryServiceCardPageObject} from 
"nightwatch/page_objects/deliveryServiceCard";
+import type {DeliveryServiceDetailPageObject} from 
"nightwatch/page_objects/deliveryServiceDetail";
+import type {DeliveryServiceInvalidPageObject} from 
"nightwatch/page_objects/deliveryServiceInvalidationJobs";
+import type {LoginPageObject} from "nightwatch/page_objects/login";
+import type {ServersPageObject} from "nightwatch/page_objects/servers";
+import type {UsersPageObject} from "nightwatch/page_objects/users";
+import {
+       CDN,
+       GeoLimit, GeoProvider, LoginRequest,
+       Protocol,
+       RequestDeliveryService,
+       ResponseCDN,
+       ResponseDeliveryService
+} from "trafficops-types";
+
+declare module "nightwatch" {
+       /**
+        * Defines the global nightwatch browser type with our types mixed in.
+        */
+       export interface NightwatchCustomPageObjects {
+               common: () => CommonPageObject;
+               deliveryServiceCard: () => DeliveryServiceCardPageObject;
+               deliveryServiceDetail: () => DeliveryServiceDetailPageObject;
+               deliveryServiceInvalidationJobs: () => 
DeliveryServiceInvalidPageObject;
+               login: () => LoginPageObject;
+               servers: () => ServersPageObject;
+               users: () => UsersPageObject;
+       }
+
+       /**
+        * Defines the additional types needed for the test environment.
+        */
+       export interface NightwatchGlobals {
+               adminPass: string;
+               adminUser: string;
+               trafficOpsURL: string;
+               apiVersion: string;
+               uniqueString: string;
+       }
 }
-const config = {
+
+const globals = {
        adminPass: "twelve12",
        adminUser: "admin",
-       trafficOpsURL: "https://localhost:6443";
+       afterEach: (browser: NightwatchBrowser, done: () => void): void => {
+               browser.end(() => {
+                       done();
+               });
+       },
+       apiVersion: "4.0",
+       before: async (done: () => void): Promise<void> => {
+               const apiUrl = 
`${globals.trafficOpsURL}/api/${globals.apiVersion}`;
+               const client = axios.create({
+                       httpsAgent: new https.Agent({
+                               rejectUnauthorized: false
+                       })
+               });
+               let accessToken = "";
+               const loginReq: LoginRequest = {
+                       p: globals.adminPass,
+                       u: globals.adminUser
+               };
+               try {
+                       const resp = await client.post(`${apiUrl}/user/login`, 
JSON.stringify(loginReq));
+                       if(resp.headers["set-cookie"]) {
+                               for (const cookie of 
resp.headers["set-cookie"]) {
+                                       if(cookie.indexOf("access_token") > -1) 
{
+                                               accessToken = cookie;
+                                               break;
+                                       }
+                               }
+                       }
+               } catch (e) {
+                       console.error((e as AxiosError).message);
+                       throw e;
+               }
+               if(accessToken === "") {
+                       console.error("Access token is not set");
+                       return Promise.reject();
+               }
+               client.defaults.headers.common = { Cookie: accessToken };
+
+               const cdn: CDN = {
+                       dnssecEnabled: false, domainName: 
`tests${globals.uniqueString}.com`, name: `testCDN${globals.uniqueString}`
+               };
+               let respCDN: ResponseCDN;
+               try {
+                       let resp = await client.post(`${apiUrl}/cdns`, 
JSON.stringify(cdn));
+                       respCDN = resp.data.response;
+
+                       const ds: RequestDeliveryService = {
+                               active: false,
+                               cacheurl: null,
+                               cdnId: respCDN.id,
+                               displayName: `test DS${globals.uniqueString}`,
+                               dscp: 0,
+                               ecsEnabled: false,
+                               edgeHeaderRewrite: null,
+                               fqPacingRate: null,
+                               geoLimit: GeoLimit.NONE,
+                               geoProvider: GeoProvider.MAX_MIND,
+                               httpBypassFqdn: null,
+                               infoUrl: null,
+                               initialDispersion: 1,
+                               ipv6RoutingEnabled: false,
+                               logsEnabled: false,
+                               maxOriginConnections: 0,
+                               maxRequestHeaderBytes: 0,
+                               midHeaderRewrite: null,
+                               missLat: 0,
+                               missLong: 0,
+                               multiSiteOrigin: false,
+                               orgServerFqdn: "http://test.com";,
+                               profileId: 1,
+                               protocol: Protocol.HTTP,
+                               qstringIgnore: 0,
+                               rangeRequestHandling: 0,
+                               regionalGeoBlocking: false,
+                               remapText: null,
+                               routingName: "test",
+                               signed: false,
+                               tenantId: 1,
+                               typeId: 1,
+                               xmlId: `testDS${globals.uniqueString}`
+                       };
+                       resp = await client.post(`${apiUrl}/deliveryservices`, 
JSON.stringify(ds));
+                       const respDS: ResponseDeliveryService = 
resp.data.response[0];
+                       console.log(`Successfully created DS 
'${respDS.displayName}'`);
+               } catch(e) {
+                       console.error((e as AxiosError).message);
+                       throw e;
+               }
+               done();
+       },
+       beforeEach: (browser: NightwatchBrowser, done: () => void): void => {
+               browser.page.login()
+                       .navigate().section.loginForm
+                       .loginAndWait(browser.globals.adminUser, 
browser.globals.adminPass);
+               // This ensures that we call done after loginAndWait is finished
+               browser.pause(1, () => {
+                       done();
+               });
+       },
+       trafficOpsURL: "https://localhost:6443";,
+       uniqueString: new Date().getTime().toString()
 };
 
-module.exports = config;
+module.exports = globals;
diff --git a/experimental/traffic-portal/nightwatch/globals/index.ts 
b/experimental/traffic-portal/nightwatch/globals/index.ts
deleted file mode 100644
index cf939cab87..0000000000
--- a/experimental/traffic-portal/nightwatch/globals/index.ts
+++ /dev/null
@@ -1,25 +0,0 @@
-/*
-* 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 { NightwatchBrowser } from "nightwatch";
-
-import type { GlobalConfig } from "./globals";
-
-/**
- * A test suite is a mapping of test descriptions to the functions that
- * implement the thereby described test.
- */
-export interface TestSuite {
-       [description: string]: (browser: NightwatchBrowser & {globals: 
GlobalConfig}) => (void | Promise<void>);
-}
diff --git a/experimental/traffic-portal/nightwatch/nightwatch.conf.js 
b/experimental/traffic-portal/nightwatch/nightwatch.conf.js
index eb6cf1d1fe..b9adc3cd4c 100644
--- a/experimental/traffic-portal/nightwatch/nightwatch.conf.js
+++ b/experimental/traffic-portal/nightwatch/nightwatch.conf.js
@@ -67,6 +67,7 @@ module.exports = {
                                        ]
                                }
                        },
+                       enable_fail_fast: false,
 
                        extends: "chrome"
                },
@@ -76,6 +77,7 @@ module.exports = {
                                browserName: "chrome"
                        },
                        disable_error_log: false,
+                       enable_fail_fast: true,
                        launch_url: "http://localhost:4200";,
                        output_folder: "nightwatch/junit",
                        screenshots: {
diff --git a/experimental/traffic-portal/nightwatch/page_objects/common.ts 
b/experimental/traffic-portal/nightwatch/page_objects/common.ts
new file mode 100644
index 0000000000..7bf6f52ede
--- /dev/null
+++ b/experimental/traffic-portal/nightwatch/page_objects/common.ts
@@ -0,0 +1,30 @@
+/*
+ * 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} from "nightwatch";
+
+/**
+ * Defines the type for the common PO
+ */
+export type CommonPageObject = EnhancedPageObject<{}, typeof 
commonPageObject.elements>;
+
+const commonPageObject = {
+       elements: {
+               snackbarEle: {
+                       selector: "simple-snack-bar"
+               }
+       }
+};
+
+export default commonPageObject;
diff --git 
a/experimental/traffic-portal/nightwatch/page_objects/deliveryServiceCard.ts 
b/experimental/traffic-portal/nightwatch/page_objects/deliveryServiceCard.ts
new file mode 100644
index 0000000000..40abb4fe6c
--- /dev/null
+++ b/experimental/traffic-portal/nightwatch/page_objects/deliveryServiceCard.ts
@@ -0,0 +1,77 @@
+/*
+* 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 {
+       EnhancedElementInstance,
+       EnhancedPageObject, EnhancedSectionInstance, NightwatchAPI
+} from "nightwatch";
+
+/**
+ * Defines the commands for the loginForm section
+ */
+interface DeliveryServiceCardCommands extends EnhancedSectionInstance, 
EnhancedElementInstance<EnhancedPageObject> {
+       expandDS(xmlId: string): Promise<boolean>;
+       viewDetails(xmlId: string): Promise<boolean>;
+}
+
+/**
+ * Defines the loginForm section
+ */
+type DeliveryServiceCardSection = 
EnhancedSectionInstance<DeliveryServiceCardCommands,
+       typeof deliveryServiceCardPageObject.sections.cards.elements>;
+
+/**
+ * Define the type for our PO
+ */
+export type DeliveryServiceCardPageObject = EnhancedPageObject<{}, {}, { 
cards: DeliveryServiceCardSection }>;
+
+const deliveryServiceCardPageObject = {
+       api: {} as NightwatchAPI,
+       sections: {
+               cards: {
+                       commands: {
+                               async expandDS(xmlId: string): Promise<boolean> 
{
+                                       return new Promise((resolve, reject) => 
{
+                                               this.click("css selector", 
`mat-card#${xmlId}`, result => {
+                                                       if (result.status === 
1) {
+                                                               reject(new 
Error(`Unable to find by css mat-card#${xmlId}`));
+                                                               return;
+                                                       }
+                                                       
this.waitForElementVisible(`mat-card#${xmlId} mat-card-content > div`,
+                                                               undefined, 
undefined, undefined, () => {
+                                                                       
resolve(true);
+                                                               });
+                                               });
+                                       });
+                               },
+                               async viewDetails(xmlId: string): 
Promise<boolean> {
+                                       await this.expandDS(xmlId);
+                                       return new Promise((resolve) => {
+                                               this.click("css selector", 
`mat-card#${xmlId} mat-card-actions > a`, () => {
+                                                       
browser.assert.urlContains("deliveryservice");
+                                                       resolve(true);
+                                               });
+                                       });
+                               }
+                       } as DeliveryServiceCardCommands,
+                       elements: {
+                       },
+                       selector: "article#deliveryservices"
+               }
+       },
+       url(): string {
+               return `${this.api.launchUrl}/core`;
+       }
+};
+
+export default deliveryServiceCardPageObject;
diff --git 
a/experimental/traffic-portal/nightwatch/page_objects/deliveryServiceDetail.ts 
b/experimental/traffic-portal/nightwatch/page_objects/deliveryServiceDetail.ts
new file mode 100644
index 0000000000..6b374bd2cd
--- /dev/null
+++ 
b/experimental/traffic-portal/nightwatch/page_objects/deliveryServiceDetail.ts
@@ -0,0 +1,60 @@
+/*
+* 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
+} from "nightwatch";
+
+/**
+ * Define the type for our PO
+ */
+export type DeliveryServiceDetailPageObject = EnhancedPageObject<{}, typeof 
deliveryServiceDetailPageObject.elements,
+{ dateInputForm: EnhancedSectionInstance<{}, typeof 
deliveryServiceDetailPageObject.sections.dateInputForm.elements> }>;
+
+const deliveryServiceDetailPageObject = {
+       elements: {
+               bandwidthChart: {
+                       selector: "canvas#bandwidthData"
+               },
+               invalidateJobs: {
+                       selector: "a#invalidate"
+               },
+               tpsChart: {
+                       selector: "canvas#tpsChartData"
+               },
+       },
+       sections: {
+               dateInputForm: {
+                       elements: {
+                               fromDate: {
+                                       selector: "input[name='fromdate']"
+                               },
+                               fromTime: {
+                                       selector: "input[name='fromtime']"
+                               },
+                               refreshBtn: {
+                                       selector: 
"button[name='timespanRefresh']"
+                               },
+                               toDate: {
+                                       selector: "input[name='todate']"
+                               },
+                               toTime: {
+                                       selector: "input[name='totime']"
+                               }
+                       },
+                       selector: "form[name='timespan']"
+               }
+       }
+};
+
+export default deliveryServiceDetailPageObject;
diff --git 
a/experimental/traffic-portal/nightwatch/page_objects/deliveryServiceInvalidationJobs.ts
 
b/experimental/traffic-portal/nightwatch/page_objects/deliveryServiceInvalidationJobs.ts
new file mode 100644
index 0000000000..e9e3f80812
--- /dev/null
+++ 
b/experimental/traffic-portal/nightwatch/page_objects/deliveryServiceInvalidationJobs.ts
@@ -0,0 +1,32 @@
+/*
+ * 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
+} from "nightwatch";
+
+/**
+ * Define the type for our PO
+ */
+export type DeliveryServiceInvalidPageObject = EnhancedPageObject<{}, typeof 
deliveryServiceInvalidPageObject.elements>;
+
+const deliveryServiceInvalidPageObject = {
+       elements: {
+               addButton: {
+                       selector: "button#new"
+               }
+       }
+};
+
+export default deliveryServiceInvalidPageObject;
diff --git a/experimental/traffic-portal/nightwatch/page_objects/login.ts 
b/experimental/traffic-portal/nightwatch/page_objects/login.ts
index f1ab698e96..69a83c8960 100644
--- a/experimental/traffic-portal/nightwatch/page_objects/login.ts
+++ b/experimental/traffic-portal/nightwatch/page_objects/login.ts
@@ -22,6 +22,7 @@ import {
 interface LoginFormSectionCommands extends EnhancedSectionInstance, 
EnhancedElementInstance<EnhancedPageObject> {
        fillOut(username: string, password: string): this;
        login(username: string, password: string): this;
+       loginAndWait(username: string, password: string): this;
 }
 
 /**
@@ -32,27 +33,28 @@ type LoginFormSection = 
EnhancedSectionInstance<LoginFormSectionCommands, typeof
 /**
  * Define the type for our PO
  */
-export type LoginPageObject = EnhancedPageObject<{}, typeof 
loginPageObject.elements, { loginForm: LoginFormSection }>;
+export type LoginPageObject = EnhancedPageObject<{}, {}, { loginForm: 
LoginFormSection }>;
 
 const loginPageObject = {
        api: {} as NightwatchAPI,
-       elements: {
-               snackbarEle: {
-                       selector: "simple-snack-bar"
-               }
-       },
        sections: {
                loginForm: {
                        commands: {
-                               fillOut(username: string, password: string): 
LoginFormSectionCommands  {
-                                        return this
+                               fillOut(username: string, password: string): 
LoginFormSectionCommands {
+                                       return this
                                                .setValue("@usernameTxt", 
username)
                                                .setValue("@passwordTxt", 
password);
                                },
                                login(username: string, password: string): 
LoginFormSectionCommands {
-                                        return this.fillOut(username, password)
+                                       return this.fillOut(username, password)
                                                .click("@loginBtn");
                                },
+                               loginAndWait(username: string, password: 
string): LoginFormSectionCommands {
+                                       const ret = this.login(username, 
password);
+                                       browser.page.common()
+                                               
.assert.containsText("@snackbarEle", "Success");
+                                       return ret;
+                               }
                        } as LoginFormSectionCommands,
                        elements: {
                                clearBtn: {
diff --git a/experimental/traffic-portal/nightwatch/tests/ds/ds.card.ts 
b/experimental/traffic-portal/nightwatch/tests/ds/ds.card.ts
new file mode 100644
index 0000000000..5c27e8f7ab
--- /dev/null
+++ b/experimental/traffic-portal/nightwatch/tests/ds/ds.card.ts
@@ -0,0 +1,29 @@
+/*
+ * 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.
+ */
+
+describe("DS Card Spec", () => {
+       it("Verify expand test", async (): Promise<void> => {
+               await browser.page.deliveryServiceCard()
+                       .navigate()
+                       .section.cards
+                       .expandDS(`testDS${browser.globals.uniqueString}`);
+       });
+
+       it("Verify detail test", async (): Promise<void> => {
+               await browser.page.deliveryServiceCard()
+                       .navigate()
+                       .section.cards
+                       .viewDetails(`testDS${browser.globals.uniqueString}`);
+       });
+});
diff --git a/experimental/traffic-portal/nightwatch/tests/ds/ds.details.spec.ts 
b/experimental/traffic-portal/nightwatch/tests/ds/ds.details.spec.ts
new file mode 100644
index 0000000000..ed240a27a2
--- /dev/null
+++ b/experimental/traffic-portal/nightwatch/tests/ds/ds.details.spec.ts
@@ -0,0 +1,51 @@
+/*
+* 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.
+*/
+
+describe("DS Detail Spec", () => {
+       beforeEach(() => {
+               browser.page.deliveryServiceCard()
+                       .navigate()
+                       .section.cards
+                       .viewDetails(`testDS${browser.globals.uniqueString}`);
+       });
+
+       it("Verify page test", (): void => {
+               const page = browser.page.deliveryServiceDetail();
+               page.assert.visible("@bandwidthChart")
+                       .assert.visible("@tpsChart")
+                       .assert.enabled("@invalidateJobs");
+
+               page.section.dateInputForm
+                       .assert.enabled("@fromDate")
+                       .assert.enabled("@fromTime")
+                       .assert.enabled("@toDate")
+                       .assert.enabled("@toTime")
+                       .assert.enabled("@refreshBtn");
+       });
+
+       it("Default values test", (): void => {
+               const page = browser.page.deliveryServiceDetail();
+               const now = new Date();
+               const nowString = now.toISOString();
+               const date = nowString.split("T")[0];
+               let time = nowString.split("T")[1].substring(0, 5);
+               time = `${(+time.split(":")[0] - 
now.getTimezoneOffset()/60).toString().padStart(2, "0")}:${time.split(":")[1]}`;
+
+               page.section.dateInputForm
+                       .assert.value("@fromDate", date)
+                       .assert.value("@fromTime", "00:00")
+                       .assert.value("@toDate", date)
+                       .assert.value("@toTime", time);
+       });
+});
diff --git 
a/experimental/traffic-portal/nightwatch/tests/ds/ds.invalidate.spec.ts 
b/experimental/traffic-portal/nightwatch/tests/ds/ds.invalidate.spec.ts
new file mode 100644
index 0000000000..a90883e49f
--- /dev/null
+++ b/experimental/traffic-portal/nightwatch/tests/ds/ds.invalidate.spec.ts
@@ -0,0 +1,63 @@
+/*
+ * 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.
+ */
+
+describe("DS Invalidation Jobs Spec", () => {
+       beforeEach(() => {
+               browser.page.deliveryServiceCard()
+                       .navigate()
+                       .section.cards
+                       .viewDetails(`testDS${browser.globals.uniqueString}`);
+               browser.page.deliveryServiceDetail()
+                       .click("@invalidateJobs")
+                       .assert.urlContains("invalidation-jobs");
+       });
+
+       it("Verify page", () => {
+               browser.page.deliveryServiceInvalidationJobs()
+                       .assert.enabled("@addButton");
+       });
+
+       it("Manage Job", async () => {
+               const page = browser.page.deliveryServiceInvalidationJobs();
+               const common = browser.page.common();
+               page
+                       .click("@addButton");
+               const startDate = new Date();
+               startDate.setDate(startDate.getDate() + 1);
+               browser.waitForElementVisible("tp-new-invalidation-job-dialog")
+                       .assert.value("input[name='startDate']", 
startDate.toLocaleDateString())
+                       .setValue("input[name='regexp']", "/invalidateMe")
+                       .click("button#submit");
+               common
+                       .assert.containsText("@snackbarEle", "created")
+                       .click("simple-snack-bar button");
+               page.assert.visible({index: 0, selector: "li.invalidation-job"})
+                       .assert.enabled({index: 0, selector: 
"li.invalidation-job button"})
+                       .assert.enabled({index: 1, selector: 
"li.invalidation-job button"});
+               page
+                       .click({index: 0, selector: "li.invalidation-job 
button"});
+               browser.waitForElementVisible("tp-new-invalidation-job-dialog")
+                       .assert.value("input[name='startDate']", 
startDate.toLocaleDateString())
+                       .assert.value("input[name='regexp']", "invalidateMe")
+                       .setValue("input[name='regexp']", "/invalidateMe2")
+                       .click("button#submit");
+               common
+                       .assert.containsText("@snackbarEle", "created")
+                       .click("simple-snack-bar button");
+               page
+                       .click({index: 1, selector: "li.invalidation-job 
button"});
+               common
+                       .assert.containsText("@snackbarEle", "was deleted");
+       });
+});
diff --git a/experimental/traffic-portal/nightwatch/tests/login.spec.ts 
b/experimental/traffic-portal/nightwatch/tests/login.spec.ts
deleted file mode 100644
index ec92912df0..0000000000
--- a/experimental/traffic-portal/nightwatch/tests/login.spec.ts
+++ /dev/null
@@ -1,50 +0,0 @@
-/*
-* 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";
-
-const suite: TestSuite = {
-       "Clear form test": browser => {
-               const page: LoginPageObject = browser.page.login();
-               page.navigate()
-                       .section.loginForm
-                       .fillOut("test", "asdf")
-                       .click("@clearBtn")
-                       .assert.containsText("@usernameTxt", "")
-                       .assert.containsText("@passwordTxt", "")
-                       .end();
-       },
-       "Incorrect password test":  browser => {
-               const page: LoginPageObject = browser.page.login();
-               page.navigate()
-                       .section.loginForm
-                       .login("test", "asdf")
-                       .assert.value("@usernameTxt", "test")
-                       .assert.value("@passwordTxt", "asdf");
-               page
-                       .assert.containsText("@snackbarEle", "Invalid")
-                       .end();
-       },
-       "Login test": browser => {
-               const page: LoginPageObject = browser.page.login();
-               page.navigate()
-                       .section.loginForm
-                       .login(browser.globals.adminUser, 
browser.globals.adminPass)
-                       .parent
-                       .assert.containsText("@snackbarEle", "Success")
-                       .end();
-       }
-};
-
-export default suite;
diff --git a/experimental/traffic-portal/nightwatch/tests/login/login.spec.ts 
b/experimental/traffic-portal/nightwatch/tests/login/login.spec.ts
new file mode 100644
index 0000000000..f8ae5ad224
--- /dev/null
+++ b/experimental/traffic-portal/nightwatch/tests/login/login.spec.ts
@@ -0,0 +1,40 @@
+/*
+* 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.
+*/
+
+describe("Login Spec", () => {
+
+       it("Clear form test", () => {
+               browser.page.login()
+                       .navigate().section.loginForm
+                       .fillOut("test", "asdf")
+                       .click("@clearBtn")
+                       .assert.containsText("@usernameTxt", "")
+                       .assert.containsText("@passwordTxt", "");
+       });
+       it("Incorrect password test", () => {
+               browser.page.login()
+                       .navigate().section.loginForm
+                       .login("test", "asdf")
+                       .assert.value("@usernameTxt", "test")
+                       .assert.value("@passwordTxt", "asdf");
+               browser.page.common()
+                       .assert.containsText("@snackbarEle", "Invalid");
+       });
+       it("Login test", () => {
+               browser.page.login()
+                       .navigate()
+                       .section.loginForm
+                       .loginAndWait(browser.globals.adminUser, 
browser.globals.adminPass);
+       });
+});
diff --git a/experimental/traffic-portal/nightwatch/tests/servers.spec.ts 
b/experimental/traffic-portal/nightwatch/tests/servers.spec.ts
deleted file mode 100644
index 301683b01e..0000000000
--- a/experimental/traffic-portal/nightwatch/tests/servers.spec.ts
+++ /dev/null
@@ -1,34 +0,0 @@
-/*
-* 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/servers/servers.spec.ts 
b/experimental/traffic-portal/nightwatch/tests/servers/servers.spec.ts
new file mode 100644
index 0000000000..606a459795
--- /dev/null
+++ b/experimental/traffic-portal/nightwatch/tests/servers/servers.spec.ts
@@ -0,0 +1,37 @@
+/*
+ * 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.
+ */
+
+/*
+* 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.
+*/
+
+describe("Servers Spec", () => {
+       it("Filter by hostname", async () => {
+               const page = browser.page.servers();
+               page.navigate()
+                       .waitForElementPresent("input[name=fuzzControl]");
+               page.section.serversTable.searchText("edge");
+               page.assert.urlContains("search=edge");
+       });
+});
diff --git a/experimental/traffic-portal/nightwatch/tests/users.spec.ts 
b/experimental/traffic-portal/nightwatch/tests/users/users.spec.ts
similarity index 73%
rename from experimental/traffic-portal/nightwatch/tests/users.spec.ts
rename to experimental/traffic-portal/nightwatch/tests/users/users.spec.ts
index a74ee7f00b..fe15afe2d9 100644
--- a/experimental/traffic-portal/nightwatch/tests/users.spec.ts
+++ b/experimental/traffic-portal/nightwatch/tests/users/users.spec.ts
@@ -11,26 +11,22 @@
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */
-import type { TestSuite } from "../globals";
-import { LoginPageObject } from "../page_objects/login";
-import type { UsersPageObject } from "../page_objects/users";
+import type { UsersPageObject } from "nightwatch/page_objects/users";
 
-const suite: TestSuite = {
-       "Filter by username": async browser => {
+describe("Users Spec", () => {
+       it("Filter by username", async () => {
                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: UsersPageObject = 
browser.waitForElementPresent("main").page.users();
-               let tbl = 
page.navigate().waitForElementPresent(".ag-row").section.usersTable;
+               page.navigate()
+                       .waitForElementPresent(".ag-row");
+               let tbl = page.section.usersTable;
                if (! await tbl.getColumnState("Username")) {
                        tbl = tbl.toggleColumn("Username");
                }
 
                tbl = tbl.searchText(username);
-               tbl.parent.assert.urlContains(`search=${username}`);
+               page.assert.urlContains(`search=${username}`);
 
                tbl.api.elements("css selector", ".ag-row:not(.ag-hidden 
.ag-row)",
                        result => {
@@ -38,11 +34,10 @@ 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)
-                                       .end();
+                               browser.assert.equal(result.value.length, 1);
                        }
                );
-       },
+       });
        // Uncomment when user details page exists
        // "View user details":  browser => {
        //      const username = browser.globals.adminUser;
@@ -57,6 +52,4 @@ const suite: TestSuite = {
        //      const userRow = 
tbl.parent.api.moveToElement(".ag-row:not(.ag-hidden .ag-row)", 2, 2, 100, 
{pointer: 0, viewport: 0});
        //      
userRow.mouseButtonClick("right").click("button[name=View-User-Details]").assert.urlContains(username);
        // }
-};
-
-export default suite;
+});
diff --git a/experimental/traffic-portal/nightwatch/tsconfig.e2e.json 
b/experimental/traffic-portal/nightwatch/tsconfig.e2e.json
index b7b2cca528..69eac9f912 100644
--- a/experimental/traffic-portal/nightwatch/tsconfig.e2e.json
+++ b/experimental/traffic-portal/nightwatch/tsconfig.e2e.json
@@ -6,7 +6,8 @@
                "target": "es5",
                "types": [
                        "node",
-                       "nightwatch"
+                       "nightwatch",
+                       "mocha"
                ],
                "rootDirs": ["./page_objects", "./tests", "./globals"]
        }
diff --git a/experimental/traffic-portal/package-lock.json 
b/experimental/traffic-portal/package-lock.json
index 39d300465c..4c7bf4a7fc 100644
--- a/experimental/traffic-portal/package-lock.json
+++ b/experimental/traffic-portal/package-lock.json
@@ -49,10 +49,12 @@
         "@types/express": "^4.17.0",
         "@types/jasmine": "~3.6.0",
         "@types/jasminewd2": "~2.0.3",
+        "@types/mocha": "^9.1.1",
         "@types/nightwatch": "2.0.1",
         "@types/node": "^14.17.34",
         "@typescript-eslint/eslint-plugin": "^5.10.0",
         "@typescript-eslint/parser": "^5.10.0",
+        "axios": "^0.27.2",
         "chromedriver": "^100.0.0",
         "codelyzer": "^6.0.0",
         "eslint": "^8.2.0",
@@ -67,6 +69,7 @@
         "karma-jasmine": "~4.0.0",
         "karma-jasmine-html-reporter": "^1.5.0",
         "nightwatch": "2.0.0-beta.3",
+        "trafficops-types": "^3.1.0-beta-6",
         "ts-node": "~8.3.0",
         "typescript": "^4.5.4"
       },
@@ -4304,6 +4307,12 @@
       "integrity": 
"sha512-YATxVxgRqNH6nHEIsvg6k2Boc1JHI9ZbH5iWFFv/MTkchz3b1ieGDa5T0a9RznNdI0KhVbdbWSN+KWWrQZRxTw==",
       "dev": true
     },
+    "node_modules/@types/mocha": {
+      "version": "9.1.1",
+      "resolved": "https://registry.npmjs.org/@types/mocha/-/mocha-9.1.1.tgz";,
+      "integrity": 
"sha512-Z61JK7DKDtdKTWwLeElSEBcWGRLY8g95ic5FoQqI9CMx0ns/Ghep3B4DfcEimiKMvtamNVULVNKEsiwV3aQmXw==",
+      "dev": true
+    },
     "node_modules/@types/nightwatch": {
       "version": "2.0.1",
       "resolved": 
"https://registry.npmjs.org/@types/nightwatch/-/nightwatch-2.0.1.tgz";,
@@ -5525,12 +5534,13 @@
       }
     },
     "node_modules/axios": {
-      "version": "0.24.0",
-      "resolved": "https://registry.npmjs.org/axios/-/axios-0.24.0.tgz";,
-      "integrity": 
"sha512-Q6cWsys88HoPgAaFAVUb0WpPk0O8iTeisR9IMqy9G8AbO4NlpVknrnQS03zzF9PGAWgO3cgletO3VjV/P7VztA==",
+      "version": "0.27.2",
+      "resolved": "https://registry.npmjs.org/axios/-/axios-0.27.2.tgz";,
+      "integrity": 
"sha512-t+yRIyySRTp/wua5xEr+z1q60QmLq8ABsS5O9Me1AsE5dfKqgnCFzwiCZZ/cGNd1lq4/7akDWMxdhVlucjmnOQ==",
       "dev": true,
       "dependencies": {
-        "follow-redirects": "^1.14.4"
+        "follow-redirects": "^1.14.9",
+        "form-data": "^4.0.0"
       }
     },
     "node_modules/axobject-query": {
@@ -6389,6 +6399,15 @@
         "node": ">=10"
       }
     },
+    "node_modules/chromedriver/node_modules/axios": {
+      "version": "0.24.0",
+      "resolved": "https://registry.npmjs.org/axios/-/axios-0.24.0.tgz";,
+      "integrity": 
"sha512-Q6cWsys88HoPgAaFAVUb0WpPk0O8iTeisR9IMqy9G8AbO4NlpVknrnQS03zzF9PGAWgO3cgletO3VjV/P7VztA==",
+      "dev": true,
+      "dependencies": {
+        "follow-redirects": "^1.14.4"
+      }
+    },
     "node_modules/ci-info": {
       "version": "3.3.0",
       "resolved": "https://registry.npmjs.org/ci-info/-/ci-info-3.3.0.tgz";,
@@ -17073,6 +17092,12 @@
         "node": ">=12"
       }
     },
+    "node_modules/trafficops-types": {
+      "version": "3.1.0-beta-6",
+      "resolved": 
"https://registry.npmjs.org/trafficops-types/-/trafficops-types-3.1.0-beta-6.tgz";,
+      "integrity": 
"sha512-CbsA8rdQCxAyBcm/MxIjvQEyYTiMpXlsDlaBTRltEro2aSMYztUf5ieFMCkMG2txi07fT+X9vdQE2f2jrgL2kQ==",
+      "dev": true
+    },
     "node_modules/traverse": {
       "version": "0.6.6",
       "resolved": "https://registry.npmjs.org/traverse/-/traverse-0.6.6.tgz";,
@@ -21371,6 +21396,12 @@
       "integrity": 
"sha512-YATxVxgRqNH6nHEIsvg6k2Boc1JHI9ZbH5iWFFv/MTkchz3b1ieGDa5T0a9RznNdI0KhVbdbWSN+KWWrQZRxTw==",
       "dev": true
     },
+    "@types/mocha": {
+      "version": "9.1.1",
+      "resolved": "https://registry.npmjs.org/@types/mocha/-/mocha-9.1.1.tgz";,
+      "integrity": 
"sha512-Z61JK7DKDtdKTWwLeElSEBcWGRLY8g95ic5FoQqI9CMx0ns/Ghep3B4DfcEimiKMvtamNVULVNKEsiwV3aQmXw==",
+      "dev": true
+    },
     "@types/nightwatch": {
       "version": "2.0.1",
       "resolved": 
"https://registry.npmjs.org/@types/nightwatch/-/nightwatch-2.0.1.tgz";,
@@ -22274,12 +22305,13 @@
       }
     },
     "axios": {
-      "version": "0.24.0",
-      "resolved": "https://registry.npmjs.org/axios/-/axios-0.24.0.tgz";,
-      "integrity": 
"sha512-Q6cWsys88HoPgAaFAVUb0WpPk0O8iTeisR9IMqy9G8AbO4NlpVknrnQS03zzF9PGAWgO3cgletO3VjV/P7VztA==",
+      "version": "0.27.2",
+      "resolved": "https://registry.npmjs.org/axios/-/axios-0.27.2.tgz";,
+      "integrity": 
"sha512-t+yRIyySRTp/wua5xEr+z1q60QmLq8ABsS5O9Me1AsE5dfKqgnCFzwiCZZ/cGNd1lq4/7akDWMxdhVlucjmnOQ==",
       "dev": true,
       "requires": {
-        "follow-redirects": "^1.14.4"
+        "follow-redirects": "^1.14.9",
+        "form-data": "^4.0.0"
       }
     },
     "axobject-query": {
@@ -22936,6 +22968,17 @@
         "https-proxy-agent": "^5.0.0",
         "proxy-from-env": "^1.1.0",
         "tcp-port-used": "^1.0.1"
+      },
+      "dependencies": {
+        "axios": {
+          "version": "0.24.0",
+          "resolved": "https://registry.npmjs.org/axios/-/axios-0.24.0.tgz";,
+          "integrity": 
"sha512-Q6cWsys88HoPgAaFAVUb0WpPk0O8iTeisR9IMqy9G8AbO4NlpVknrnQS03zzF9PGAWgO3cgletO3VjV/P7VztA==",
+          "dev": true,
+          "requires": {
+            "follow-redirects": "^1.14.4"
+          }
+        }
       }
     },
     "ci-info": {
@@ -30961,6 +31004,12 @@
         "punycode": "^2.1.1"
       }
     },
+    "trafficops-types": {
+      "version": "3.1.0-beta-6",
+      "resolved": 
"https://registry.npmjs.org/trafficops-types/-/trafficops-types-3.1.0-beta-6.tgz";,
+      "integrity": 
"sha512-CbsA8rdQCxAyBcm/MxIjvQEyYTiMpXlsDlaBTRltEro2aSMYztUf5ieFMCkMG2txi07fT+X9vdQE2f2jrgL2kQ==",
+      "dev": true
+    },
     "traverse": {
       "version": "0.6.6",
       "resolved": "https://registry.npmjs.org/traverse/-/traverse-0.6.6.tgz";,
diff --git a/experimental/traffic-portal/package.json 
b/experimental/traffic-portal/package.json
index 5ebb6953aa..7164ce906a 100644
--- a/experimental/traffic-portal/package.json
+++ b/experimental/traffic-portal/package.json
@@ -34,6 +34,7 @@
     "start": "ng serve",
     "build": "ng build",
     "test": "ng test",
+    "clean": "rm -rf out-tsc nightwatch/junit nightwatch/screens tests_output 
logs",
     "coverage": "ng test --code-coverage",
     "test:ci": "ng test --watch=false --browsers=ChromeHeadlessCustom",
     "coverage:ci": "ng test --code-coverage --watch=false 
--browsers=ChromeHeadlessCustom",
@@ -88,10 +89,12 @@
     "@types/express": "^4.17.0",
     "@types/jasmine": "~3.6.0",
     "@types/jasminewd2": "~2.0.3",
+    "@types/mocha": "^9.1.1",
     "@types/nightwatch": "2.0.1",
     "@types/node": "^14.17.34",
     "@typescript-eslint/eslint-plugin": "^5.10.0",
     "@typescript-eslint/parser": "^5.10.0",
+    "axios": "^0.27.2",
     "chromedriver": "^100.0.0",
     "codelyzer": "^6.0.0",
     "eslint": "^8.2.0",
@@ -106,6 +109,7 @@
     "karma-jasmine": "~4.0.0",
     "karma-jasmine-html-reporter": "^1.5.0",
     "nightwatch": "2.0.0-beta.3",
+    "trafficops-types": "^3.1.0-beta-6",
     "ts-node": "~8.3.0",
     "typescript": "^4.5.4"
   },
diff --git 
a/experimental/traffic-portal/src/app/core/deliveryservice/deliveryservice.component.html
 
b/experimental/traffic-portal/src/app/core/deliveryservice/deliveryservice.component.html
index 72653aa3a9..94c253987d 100644
--- 
a/experimental/traffic-portal/src/app/core/deliveryservice/deliveryservice.component.html
+++ 
b/experimental/traffic-portal/src/app/core/deliveryservice/deliveryservice.component.html
@@ -35,14 +35,14 @@ limitations under the License.
                                <input matInput [formControl]="toTime" 
type="time" title="To (time)" name="totime">
                        </mat-form-field>
                </div>
-               <button mat-raised-button>Refresh</button>
+               <button name="timespanRefresh" 
mat-raised-button>Refresh</button>
        </form>
        <mat-divider></mat-divider>
-       <canvas linechart chartTitle="Bandwidth of Cache Tiers" 
[chartDataSets]="bandwidthData" chartYAxisLabel="Bandwidth (Kilobytes Per 
Second)" chartXAxisLabel="Date/Time" [chartDisplayLegend]="true" 
chartType="time">
+       <canvas linechart chartTitle="Bandwidth of Cache Tiers" 
id="bandwidthData" [chartDataSets]="bandwidthData" chartYAxisLabel="Bandwidth 
(Kilobytes Per Second)" chartXAxisLabel="Date/Time" [chartDisplayLegend]="true" 
chartType="time">
                Your browser does not support canvases or Javascript. Normally, 
this would be a graph of bandwidth data.
        </canvas>
        <mat-divider></mat-divider>
-        <canvas linechart chartTitle="Transactions at the Edge Tier" 
[chartDataSets]="tpsChartData" chartYAxisLabel="Transactions Per Second" 
chartXAxisLabel="Date/Time" [chartDisplayLegend]="true" chartType="time">
+        <canvas linechart chartTitle="Transactions at the Edge Tier" 
id="tpsChartData" [chartDataSets]="tpsChartData" chartYAxisLabel="Transactions 
Per Second" chartXAxisLabel="Date/Time" [chartDisplayLegend]="true" 
chartType="time">
                Your browser does not support canvases or Javascript. Normally, 
this would be a graph of transaction data.
        </canvas>
        <a mat-fab id="invalidate" 
routerLink="/core/deliveryservice/{{deliveryservice.id}}/invalidation-jobs" 
title="invalidate content" aria-label="invalidate content">
diff --git 
a/experimental/traffic-portal/src/app/core/ds-card/ds-card.component.html 
b/experimental/traffic-portal/src/app/core/ds-card/ds-card.component.html
index 323f9edec3..4113699c93 100644
--- a/experimental/traffic-portal/src/app/core/ds-card/ds-card.component.html
+++ b/experimental/traffic-portal/src/app/core/ds-card/ds-card.component.html
@@ -11,7 +11,7 @@ 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.
 -->
-<mat-card [ngClass]="{'inactive': !deliveryService.active, 'open': open, 
'first': first, 'last': last}">
+<mat-card id="{{deliveryService.xmlId}}" [ngClass]="{'inactive': 
!deliveryService.active, 'open': open, 'first': first, 'last': last}">
        <mat-card-title-group (click)="toggle()">
                
<mat-card-title>{{deliveryService.displayName}}{{deliveryService.active ? '' : 
' (inactive)'}}
                        <a href="{{deliveryService.infoUrl}}" 
*ngIf="deliveryService.infoUrl" class="color-accent-inverted info" 
rel="noopener" target="_blank" title="More Information"><fa-icon 
[icon]="infoIcon"></fa-icon></a>
diff --git 
a/experimental/traffic-portal/src/app/core/invalidation-jobs/invalidation-jobs.component.html
 
b/experimental/traffic-portal/src/app/core/invalidation-jobs/invalidation-jobs.component.html
index e6318028c5..8e475cfc9e 100644
--- 
a/experimental/traffic-portal/src/app/core/invalidation-jobs/invalidation-jobs.component.html
+++ 
b/experimental/traffic-portal/src/app/core/invalidation-jobs/invalidation-jobs.component.html
@@ -13,7 +13,7 @@ limitations under the License.
 -->
 <tp-header title="{{deliveryservice ? deliveryservice.displayName : 
'Loading'}} - Content Invalidation Jobs"></tp-header>
 <ul>
-       <li *ngFor="let j of jobs">
+       <li class="invalidation-job" *ngFor="let j of jobs">
                <code>{{j.assetUrl}}</code> (active from <time 
[dateTime]="j.startTime">{{j.startTime | date:'medium'}}</time> to <time 
[dateTime]="endDate(j)">{{endDate(j) | date:'medium'}})</time>
                <button
                        mat-icon-button

Reply via email to