Skip to content

Commit

Permalink
feat: [TERR-313][TERR-325] Integration test harness (#26)
Browse files Browse the repository at this point in the history
* feat/MAP-160
- initial commit for integration testing and auth for Grafana
- plugin testing code TBD

* feat/MAP-160
- updated Makefile with e2e targets
- updated package.json with e2e targets
- added setup and plugin dependencies for auto user sign in for e2e testing in playwright.config file
- notes on test configuration and Yarn setup added to
README
- removed commented code
- added e2e.config.json file
- data-testids attributes added to initial set of e2e tests
for targets of Grafana show/hide sliding switches for View Options
- added plugin.spec.ts for e2e testing

* feat/MAP-160
- refactored testid into constants module
- updated references to import of testids in
EditingInterface and MapCanvas components away from
e2e.config to constants module
- fixed use of controls vs control group click events in
plugin.spec
- updated dist modules

* feat/MAP-160
- added makefile autogenerated e2e/grafana-docker JSON file to .gitignore
- added docker-compose to Makefile test:e2e recipe and inpsection of instance info for tests
- added CI=1 on the fly environment variables to e2e and e2e:ui scripts in package.json
- added node-fetch depedency to package.json for fetching from Grafana API endpoints
-  refactored playwright config to use auth rather than setup
as a dependency
- updated yarn.lock file
- updated auth.setup to by pass reset of default password step
during testing
- refactored getHostInfo function into its own script/module
- additional parmeters as specified in e2e.config, to be removed
- added getFolderDashboardTargets to support test fixture property setting to folderDashboardInit script
- added getDashboard, getCurrentUser, createDashboard, and createFolder functions to Grafana API service module
- added networkMapPanel.json as a base esnet-networkmap-panel
- added in plugin-def module for defining PluginTest types and fixture default values
- added getEditNetowrkMapPanelUrl function to plugin.spec
script
- removed commented code
- added interfaces for Panel and Targets

* feat/MAP-160
- removed unneeded console.log lines

* feat/MAP-160
- removed unused variable
- removed homepage and protocolHostPort props from
e2e.config
- refactored targetPanel to target first of type "esnet-networkmap-panel" in e2e.config
- added orgId to fixtures initializations for test params and ITargets interface
in getFolderDashboardTargets fn in folderDashboardInit
module
- set default value of orgId to -1
- added getHomepageUrl fn in plugin.spec

* MAP-160
- removed cap on number of workers to 1 for e2e testing
- removed jest.config
- removed dependencies on jest in package.json
- updated yarn.lock

* MAP-160
- updated yarn.lock
- removed jest.config from .npmignore
- remove jest and ts-jest from dependencies in package.json
- moved targetDashboardUid setting of 'pending' to before
if statement
- updated module.js and plugin.json

* MAP-160
- removed .yarnrc and .yarnrc.yml (reverting back to 1.22.21)
- removed unneded package-lock.json (seems to have come from .npm)
- pinned ver of @swc/core to 1.3.75 for compatibility with Node >= 20
- bumped engines requirement for node >=20 and yarn >= 1.22.21 and added preinstall script hook for enforcing use
- updated yarn.lock
- removed yarn setup section in README.md (caused too many issues and belongs more in docs/development.md)
- updated yarn.lock

* MAP-160
- added dependencies for eslint-plugin-react and eslint-plugin-jsdoc to meet minimum requirement for dev mode
- updated yarn.lock file
- readded most generated dist files
- converted functions in module.ts to use arrow notation
- fixed imports using local files with dot slash prefix in MapPanel.tsx

* MAP-160
- updates related to resolving _this is not defined

* MAP-160
- rolled back version of @playwright/test to 1.34.3 (and included in dev dependencies)
- updated node dependency to work with node 18
- updated lock file
- added missing step WRT to install playwright browsers
- updated config.info module in ./e2e to build basicAuthHeader using Buffer.from and Buffer.toString instead of old btoa method
- added binding of EsMap.update method via this.update.bind(this).
- aligned and pinned packages from @grafana to agree with versions as specified in Grafana 8.3.0

* MAP-160
- updated lock file
- updated @grafana/eslint-config version to pinned 7.0.0 in package.json
- removed node-fetch related packages from package.json (relying on node v18, aka lts/hydrogen built ins)

* MAP-160
- changed required yarn version to 1.22.0 or greater

* MAP-160
- removed skipped tests relating to dashboard navigation (not directly within scope of plugin testing)

* MAP-160
- removed commented code
- updated config.info.ts in e2e testing to use port in first available entry under portKeys in generated docker JSON output

* MAP-160
- fixed bad version references to Node 18 and Yarn in development.md

* MAP-160
- moved documentation on testing out of README.md into development.md

* MAP-160
- updated dist copy of the README.md file to reflect that in the project root

All notes here correspond to the original ticket [MAP-160] as well as consequent tickets [TERR-313] and [TERR-325]
  • Loading branch information
sanchezelton authored Mar 20, 2024
1 parent bf9406e commit 5d7c0b5
Show file tree
Hide file tree
Showing 33 changed files with 2,347 additions and 2,007 deletions.
13 changes: 12 additions & 1 deletion .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,9 @@ yarn-error.log*

node_modules/

# local yarn
.yarn

# Runtime data
pids
*.pid
Expand All @@ -35,4 +38,12 @@ e2e-results/
# Editors
.idea
.vscode
.DS_store
.DS_store

# Playwright
playwright*
test-results
tests-examples

# autogenerated by Makefile for e2e
e2e/grafana-docker.json
1 change: 0 additions & 1 deletion .npmignore
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,6 @@ README-JAMES.md
.prettierrc.js
*/*/old/*
docs/*
jest.config.js
src/components/lib/.pnpm-debug.log
*/yarn-error.log
yarn-error.log
24 changes: 22 additions & 2 deletions Makefile
Original file line number Diff line number Diff line change
@@ -1,8 +1,11 @@
NODE=`which node`
NPM=`which npm`
GRAFANA_PATH="node_modules/@grafana/toolkit/bin/grafana-toolkit.js"
BREW=/usr/local/bin/brew
BREW=$(which brew)
CLI_TOOLS_PATH=~/work/cli-tools/stardust_map_topology
PROJECT_DIR := $(dir $(realpath $(lastword $(MAKEFILE_LIST))))
CONTAINER_NAME=esnet-networkmap-panel
CONTAINER_ID=$(shell docker ps -f name=$(CONTAINER_NAME) -q)

.PHONY: prod
prod:
Expand All @@ -23,10 +26,27 @@ run:
restart:
$(BREW) services restart grafana

.PHONY: compose
compose:
# start grafana docker instance and map project to plugin directory on instance
docker-compose up -d
# get instance info
docker inspect $(CONTAINER_NAME) > $(PROJECT_DIR)/e2e/grafana-docker.json

.PHONY: test
test:
test: compose
yarn test
yarn e2e

.PHONY: test\:component
test\:component:
yarn test

.PHONY: test\:e2e
test\:e2e: compose
# run e2e tests
yarn e2e

.PHONY: testignore
testignore:
@echo ""
Expand Down
6 changes: 6 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,12 @@ The topology can be either geographically referenced against a geographic tilese

The plugin will plot traffic information on the network topology, showing bi-directional traffic flow between nodes.

## Table of Contents

[Dashboard JSON](#Dashboard-JSON)

[Introductory Tutorial](#Introductory-Tutorial)

## Dashboard JSON

The Introductory Tutorial below is complete, but lengthy. In an effort to get to a demonstration state a bit more quickly, try using this Grafana Dashboard JSON snippet:
Expand Down
2 changes: 1 addition & 1 deletion dist/MapPanel.d.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
import React, { Component } from 'react';
import { PanelProps } from '@grafana/data';
import { MapOptions } from 'types';
import { MapOptions } from './types';
import 'components/MapCanvas.component.js';
interface Props extends PanelProps<MapOptions> {
}
Expand Down
2 changes: 1 addition & 1 deletion dist/MapPanel.d.ts.map

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

6 changes: 6 additions & 0 deletions dist/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,12 @@ The topology can be either geographically referenced against a geographic tilese

The plugin will plot traffic information on the network topology, showing bi-directional traffic flow between nodes.

## Table of Contents

[Dashboard JSON](#Dashboard-JSON)

[Introductory Tutorial](#Introductory-Tutorial)

## Dashboard JSON

The Introductory Tutorial below is complete, but lengthy. In an effort to get to a demonstration state a bit more quickly, try using this Grafana Dashboard JSON snippet:
Expand Down
2 changes: 1 addition & 1 deletion dist/components/MapCanvas.component.d.ts.map

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

12 changes: 12 additions & 0 deletions dist/constants.d.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
export default testIds;
declare namespace testIds {
const sidebar: string;
const map: string;
const zoomInBtn: string;
const zoomOutBtn: string;
const addEdgeBtn: string;
const addNodeBtn: string;
const editEdgeToggleBtn: string;
const editNodeToggleBtn: string;
}
//# sourceMappingURL=constants.d.ts.map
1 change: 1 addition & 0 deletions dist/constants.d.ts.map

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

2 changes: 1 addition & 1 deletion dist/module.js.map

Large diffs are not rendered by default.

105 changes: 90 additions & 15 deletions docs/development.md
Original file line number Diff line number Diff line change
@@ -1,9 +1,17 @@

## Development Notes

This project was built in Node 14.21.3 (LTS Fermium) and intended to be built using Yarn (1.22.21).
This project was built in Node 18.19.1 (LTS Hydrogen) and must built using Yarn (1.22.0 or higher).

### Local development
## Table of Contents

[Local Development](#local-development)

[Troubleshooting](#troubleshooting)

[Testing Requirements](#testing-requirements)

## Local development

Pre-requisite: For local development, Grafana must be running locally as a service or as a Docker container.

Expand All @@ -15,18 +23,21 @@ Pre-requisite: For local development, Grafana must be running locally as a servi

Project setup:

Pre-requisites: Both Node 14.21.3 (LTS Fermium) and Yarn must be installed. Later builds may not build or your mileage may vary.
Pre-requisites: Both Node 18.19.1 (LTS Hydrogen) and Yarn 1.22.0 or higher must be installed. Other versions may not build
and when they do, stability issues, unexpected failures, or loss of functionality may occur.

It is recommended to use [nvm](https://github.com/nvm-sh/nvm) to install and manage your Node versions.

2. Install required dependencies via Yarn, as normal:

```
```sh
$ yarn install
```

3. Configure Grafana to read plugins from the parent directory of the project.

When installed as a service, the most likely place for the config file is `/usr/local/etc/grafana/grafana.ini`
For exmaple, if the project is located in /Users/myuser/grafana-plugins/grafana-esnet-networkmap-panel, set the
For example, if the project is located in /Users/myuser/grafana-plugins/grafana-esnet-networkmap-panel, set the
plugins value to the parent directory:

```grafana.ini
Expand All @@ -40,35 +51,99 @@ Mapping only needs to be done once. Restart Grafana or the container after mappi

4. Build the project once using `make dev`. This will create source maps permit setting of breakpoints in Chrome Debugger during development. Note that this must be run at least once and may require rerunning periodically if the plugin is being developed in the Grafana webapp.

5. Build the project using `make prod` (`prod` is not a typo). A failure during signing is expected for local development.
5. Install Playwright browsers for testing (this only needs to be done once).

```sh
$ npx playwright install
```

6. Build the project using `make prod` (`prod` is not a typo). A failure during signing is expected for local development.

This will update the files in the dist directory.

```
```sh
$ make prod
```

3. Run the Yarn script `build_dts` to complete the process of building without signing.
7. Run the Yarn script `build_dts` to complete the process of building without signing.

This will update the files in the dist directory and readies the contents for Grafana. If Grafana is already running,
and already has its plugins directory mapped (see step 4), there is no need to restart.

```
```sh
$ yarn run build_dts
```

4. Open a browser and navigate to your Grafana instance.
8. Open a browser and navigate to your Grafana instance.

5. Login and create a new dashboard or navigate to the default one.
9. Login and create a new dashboard or navigate to the default one.

6. Add a new panel to the dashboard. The plugin should be ready for adding as "Network Map Panel".
10. Add a new panel to the dashboard. The plugin should be ready for adding as "Network Map Panel".

7. Enter edit mode in the newly added panel. You should be able to view a map (and it's sidebar when enabled) plus work with the
Grafana sidebar on the right to configure the panel.
11. Enter edit mode in the newly added panel. You should be able to view a map (and its sidebar when enabled) plus work with the
Grafana sidebar on the right to configure the panel. Follow the instructions in README.md to configure the Grafana panel.

### Troubleshooting

Q1. I cannot set breakpoints in TypeScript files in the Chrome debugger. What is going on?

A1. It is possible that the project was built using `make prod` without running `make dev` first. `make dev` will create the source
maps files in the dist directory allowing setting of breakpoints within TypeScript.
maps files in the dist directory allowing setting of breakpoints within TypeScript.

## Testing Requirements

[Playwright](https://https://playwright.dev/) and Karma-driven [ShouldJS](https://shouldjs.github.io/) are used to implement integration
and unit testing for the plugin respectively, following as closely as needed to the implementation utilized by Grafana's plugin-e2e
package.

### Test configuration

You must specify a username and password as a JSON object under playwright/.auth/credentials.json. At the same time,
e2e/e2e.config.json should be configured to target a particular dashboard for running the tests upon. Use the included
e2e.config.json.sample as a basis for your own e2e.config.json.

Testids should not have to be changed, although you have the option of doing so. The only requirement is that all testids
therein be unique.

Sample playwright/.auth/credentials.json:

```json
{
"username": "myGrafanaUser",
"password": "myGrafanaPassword"
}
```

### Test Browsers

Browser packages utilized by Playwright must be installed globally in order to run the e2e tests. To install them, open a shell command prompt
and enter:

```sh
$ npx playwright install
```

### Test Execution and Reporting

To run both component and integration tests:

```sh
$ make test
```

Prior to running the tests,

You also have the option of running component and integration tests separately, either using make or Yarn (both pairs
of shell commands below do the same thing.)

```sh
$ make test:component
$ make test:e2e

$ yarn test
$ yarn e2e
```

Integration tests are written with the assumption the Playwright's own browsers are globally installed in the system
using `npx playwright install`. Reconfigure in the playwright.config.ts file if a different browser is
preferred. Test suite files (*.spec.js) are included in the e2e folder.
31 changes: 31 additions & 0 deletions e2e/auth.setup.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
import { expect, Page } from '@playwright/test';
import { pluginTest as setup } from './plugin-def';
import credentials from '../playwright/.auth/credentials.json';
import { getHostInfo } from './config.info';

const authFile = 'playwright/.auth/user.json';

setup('authenticate', async ({ page }: { page: Page }) => {
const { protocolHostPort } = getHostInfo(credentials);
// Perform authentication steps. Replace these actions with your own.
await page.goto(`${protocolHostPort}/login`);
await page.getByLabel('Username input field').fill(credentials.username);
await page.getByLabel('Password input field').fill(credentials.password);
await page.getByLabel('Login button').click();

const skipBtn = await page.getByLabel('Skip');
if (!!skipBtn) {
await skipBtn.click();
}

// Wait until the page receives the cookies.
//
// Sometimes login flow sets cookies in the process of several redirects.
// Wait for the final URL to ensure that the cookies are actually set.
// Alternatively, you can wait until the page reaches a state where all cookies are set.
await page.waitForURL("**/?orgId=1");
await expect(page.getByTestId('sidemenu')).toBeVisible();
// End of authentication steps.

await page.context().storageState({ path: authFile });
});
44 changes: 44 additions & 0 deletions e2e/config.info.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,44 @@
import dockerInfo from '../e2e/grafana-docker.json';
import e2eConfig from '../e2e/e2e.config.json';

const moduleName = 'config.info';
const targetGrafanaInstanceName = e2eConfig.grafanaInstanceName || "grafana";
const grafanaInfo = (dockerInfo as Array<any>).find(nfo => nfo.Name == `/${targetGrafanaInstanceName}`);
if (!grafanaInfo) {
const configInstr = 'Configure one in e2e.config.json under the key "grafanaInstanceName" or use "grafana" as the name';
const err = `${moduleName}: No Docker instance named "${targetGrafanaInstanceName}" could be found. ${configInstr}`;
throw new Error(err);
}
const { IPAddress, Ports } = grafanaInfo?.NetworkSettings;

/**
* Fetches the basic auth header and URL to the configured Grafana server.
*
* @param {{username: string, password: string}} credentials
* @returns {{ protocolHostPort: string, basicAuthHeader: {[headerName: string]: string}}}
*/
export const getHostInfo = (credentials: {username: string, password: string}) => {
const fnName = 'auth.setup.getHostInfo';
let protocolHostPort;
const portKeys = Object.keys(Ports);

if (portKeys.length > 0) {
const portInfo = Ports[portKeys[0]] as {
HostIp: string;
HostPort: string;
}[];
if (portInfo.length > 0) {
protocolHostPort = `http://${IPAddress || 'localhost'}:${portInfo[0].HostPort}`;
const credentialsBuf = Buffer.from(`${credentials.username}:${credentials.password}`, 'base64');
const basicAuthHeader = {
"Authorization": `Basic ${credentialsBuf.toString('base64')}`
};
const result = { protocolHostPort, basicAuthHeader};
return result;
} else {
throw new Error(`${fnName}: cannot derive port number from Docker NetworkSettings.Ports inspection:\n${JSON.stringify(Ports)}`)
}
} else {
throw new Error(`${fnName}: cannot derive port number from Docker NetworkSettings.Ports inspection:\n${JSON.stringify(Ports)}`)
}
};
6 changes: 6 additions & 0 deletions e2e/e2e.config.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
{
"targetPanelType": "esnet-networkmap-panel",
"targetDashboard": "network-map-test-dashboard",
"targetFolder": "network-map-test-folder",
"grafanaInstanceName": "esnet-networkmap-panel"
}
Loading

0 comments on commit 5d7c0b5

Please sign in to comment.