[APM] Minimal e2e setup with Cypress (#43463) (#46338)

* APM E2E with own package.json

* Ignore cypress folder

* Add cypress/apm as separate ts project

* Exclude from parent tsconfig

* Add p-limit as dep

* Fix readme

* Fix prettier command

* Resolve feedback

* Move date range into `loginAndWaitForPage`

* Remove redundant file

* Fixed lint errors

* Remove uneeded `data-cy` attributes

* Fix snapshots

# Conflicts:
#	x-pack/legacy/plugins/apm/readme.md
This commit is contained in:
Søren Louv-Jansen 2019-09-23 19:17:50 +02:00 committed by GitHub
parent 81bc4f3e67
commit 0fa6ed315b
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
21 changed files with 4988 additions and 13 deletions

View file

@ -38,6 +38,7 @@ bower_components
/x-pack/legacy/plugins/infra/common/graphql/types.ts
/x-pack/legacy/plugins/infra/public/graphql/types.ts
/x-pack/legacy/plugins/infra/server/graphql/types.ts
/x-pack/legacy/plugins/apm/cypress/**/snapshots.js
**/graphql/types.ts
**/*.js.snap
!/.eslintrc.js

View file

@ -125,6 +125,7 @@ export default class ClusterManager {
resolve(path, 'scripts'),
resolve(path, 'docs'),
resolve(path, 'legacy/plugins/siem/cypress'),
resolve(path, 'legacy/plugins/apm/cypress'),
resolve(path, 'x-pack/legacy/plugins/canvas/canvas_plugin_src') // prevents server from restarting twice for Canvas plugin changes
),
[]

View file

@ -32,6 +32,7 @@ export const PROJECTS = [
resolve(REPO_ROOT, 'x-pack/legacy/plugins/siem/cypress/tsconfig.json'),
'siem/cypress'
),
new Project(resolve(REPO_ROOT, 'x-pack/legacy/plugins/apm/cypress/tsconfig.json'), 'apm/cypress'),
// NOTE: using glob.sync rather than glob-all or globby
// because it takes less than 10 ms, while the other modules

View file

@ -0,0 +1,63 @@
### How to run
_Note: Run the following commands from `kibana/x-pack/legacy/plugins/apm/cypress`._
#### Interactive mode
```
yarn cypress open
```
#### Headless mode
```
yarn cypress run
```
### Connect to Elasticsearch on Cloud (internal devs only)
Update kibana.yml with the [cloud credentials](https://p.elstc.co/paste/nRxc9Fuq#0GKJvmrJajnl-PjgBZSnpItKaixWgPb2xn6DCyGD6nw). The cloud instance contains the static data set
### Kibana
#### `--no-base-path`
Kibana must be started with `yarn start --no-base-path`
#### Content Security Policy (CSP) Settings
Your local or cloud Kibana server must have the `csp.strict: false` setting
configured in `kibana.dev.yml`, or `kibana.yml`, as shown in the example below:
```yaml
csp.strict: false
```
The above setting is required to prevent the _Please upgrade
your browser_ / _This Kibana installation has strict security requirements
enabled that your current browser does not meet._ warning that's displayed for
unsupported user agents, like the one reported by Cypress when running tests.
### Ingest static data into Elasticsearch via APM Server
1. Download [static data file](https://storage.googleapis.com/apm-ui-e2e-static-data/events.json)
2. Post to APM Server
```
node ingest-data/replay.js --server-url http://localhost:8200 --secret-token abcd --events ./events.json
```
### Generate static data
Capture data from all agents with [apm-integration-testing](https://github.com/elastic/apm-integration-testing):
```
./scripts/compose.py start master --all --apm-server-record
```
To copy the captured data from the container to the host:
```
docker cp localtesting_8.0.0_apm-server-2:/app/events.json .
```

View file

@ -0,0 +1,14 @@
/*
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
* or more contributor license agreements. Licensed under the Elastic License;
* you may not use this file except in compliance with the Elastic License.
*/
// eslint-disable-next-line spaced-comment
/// <reference types="cypress" />
declare namespace Cypress {
interface Chainable<Subject> {
snapshot: () => {};
}
}

View file

@ -0,0 +1,14 @@
{
"baseUrl": "http://localhost:5601",
"video": false,
"trashAssetsBeforeRuns": false,
"fileServerFolder": "../",
"fixturesFolder": "./fixtures",
"integrationFolder": "./integration",
"pluginsFile": "./plugins/index.js",
"screenshotsFolder": "./screenshots",
"supportFile": "./support/index.ts",
"videosFolder": "./videos",
"useRelativeSnapshots": true
}

View file

@ -0,0 +1,5 @@
{
"name": "Using fixtures to represent data",
"email": "hello@cypress.io",
"body": "Fixtures are a great way to mock data for responses to routes"
}

View file

@ -0,0 +1,93 @@
/*
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
* or more contributor license agreements. Licensed under the Elastic License;
* you may not use this file except in compliance with the Elastic License.
*/
/* eslint-disable import/no-extraneous-dependencies */
/* eslint-disable no-console */
/**
* This script is useful for ingesting previously generated APM data into Elasticsearch via APM Server
*
* You can either:
* 1. Download a static test data file from: https://storage.googleapis.com/apm-ui-e2e-static-data/events.json
* 2. Or, generate the test data file yourself by following the steps in: https://github.com/sqren/kibana/blob/master/x-pack/legacy/plugins/apm/cypress/README.md#how-to-generate-static-data
*
* Run the script:
*
* node replay.js --server-url <apm server url> --secret-token <apm server secret token> --events ./events.json
*
************/
const { promisify } = require('util');
const fs = require('fs');
const path = require('path');
const axios = require('axios');
const readFile = promisify(fs.readFile);
const pLimit = require('p-limit');
const { argv } = require('yargs');
const APM_SERVER_URL = argv.serverUrl;
const SECRET_TOKEN = argv.secretToken;
const EVENTS_PATH = argv.events;
if (!APM_SERVER_URL) {
console.log('`--server-url` is required');
process.exit(1);
}
if (!EVENTS_PATH) {
console.log('`--events` is required');
process.exit(1);
}
const delay = ms => new Promise(resolve => setTimeout(resolve, ms));
async function insertItem(item) {
try {
const url = `${APM_SERVER_URL}${item.url}`;
console.log(Date.now(), url);
const headers = {
'content-type': 'application/x-ndjson'
};
if (SECRET_TOKEN) {
headers.Authorization = `Bearer ${SECRET_TOKEN}`;
}
await axios({
method: item.method,
url,
headers,
data: item.body
});
// add delay to avoid flooding the queue
return delay(500);
} catch (e) {
console.log('an error occurred');
if (e.response) {
console.log(e.response.data);
} else {
console.log('error', e);
}
}
}
async function init() {
const content = await readFile(path.resolve(__dirname, EVENTS_PATH));
const items = content
.toString()
.split('\n')
.filter(item => item)
.map(item => JSON.parse(item))
.filter(item => item.url === '/intake/v2/events');
const limit = pLimit(20); // number of concurrent requests
await Promise.all(items.map(item => limit(() => insertItem(item))));
}
init().catch(e => {
console.log('An error occurred:', e);
});

View file

@ -0,0 +1,56 @@
/*
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
* or more contributor license agreements. Licensed under the Elastic License;
* you may not use this file except in compliance with the Elastic License.
*/
// eslint-disable-next-line spaced-comment
/// <reference types="cypress" />
import { loginAndWaitForPage } from './helpers';
describe('When clicking opbeans-go service', () => {
before(() => {
// open service overview page
loginAndWaitForPage(`/app/apm#/services`);
// show loading text for services
cy.contains('Loading...');
// click opbeans-go service
cy.get(':contains(opbeans-go)')
.last()
.click({ force: true });
});
it('should redirect to correct path with correct params', () => {
cy.url().should('contain', `/app/apm#/services/opbeans-go/transactions`);
cy.url().should('contain', `transactionType=request`);
});
describe('transaction duration charts', () => {
it('should have correct y-axis ticks', () => {
const yAxisTick =
'[data-cy=transaction-duration-charts] .rv-xy-plot__axis--vertical .rv-xy-plot__axis__tick__text';
cy.get(yAxisTick)
.eq(2)
.invoke('text')
.snapshot();
cy.get(yAxisTick)
.eq(1)
.invoke('text')
.snapshot();
cy.get(yAxisTick)
.eq(0)
.invoke('text')
.snapshot();
});
});
describe('TPM charts', () => {});
describe('Transaction group list', () => {});
});

View file

@ -0,0 +1,50 @@
/*
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
* or more contributor license agreements. Licensed under the Elastic License;
* you may not use this file except in compliance with the Elastic License.
*/
/* eslint-disable import/no-extraneous-dependencies */
import { safeLoad } from 'js-yaml';
const RANGE_FROM = '2019-09-04T18:00:00.000Z';
const RANGE_TO = '2019-09-05T06:00:00.000Z';
const BASE_URL = Cypress.config().baseUrl;
/**
* Credentials in the `kibana.dev.yml` config file will be used to authenticate with Kibana
*/
const KIBANA_DEV_YML_PATH = '../../../../../config/kibana.dev.yml';
/** The default time in ms to wait for a Cypress command to complete */
export const DEFAULT_TIMEOUT = 30 * 1000;
export function loginAndWaitForPage(url: string) {
// read the login details from `kibana.dev.yml`
cy.readFile(KIBANA_DEV_YML_PATH).then(kibanaDevYml => {
const config = safeLoad(kibanaDevYml);
const username = config['elasticsearch.username'];
const password = config['elasticsearch.password'];
const hasCredentials = username && password;
cy.log(
`Authenticating via config credentials from "${KIBANA_DEV_YML_PATH}". username: ${username}, password: ${password}`
);
const options = hasCredentials
? {
auth: { username, password }
}
: {};
const fullUrl = `${BASE_URL}${url}?rangeFrom=${RANGE_FROM}&rangeTo=${RANGE_TO}`;
cy.visit(fullUrl, options);
});
cy.viewport('macbook-15');
// wait for loading spinner to disappear
cy.get('.kibanaLoaderWrap', { timeout: DEFAULT_TIMEOUT }).should('not.exist');
}

View file

@ -0,0 +1,12 @@
module.exports = {
"When clicking opbeans-go service": {
"transaction duration charts": {
"should have correct y-axis ticks": {
"1": "3.7 min",
"2": "1.8 min",
"3": "0.0 min"
}
}
},
"__version": "3.4.1"
}

View file

@ -0,0 +1,21 @@
{
"name": "apm-cypress",
"version": "1.0.0",
"main": "index.js",
"license": "MIT",
"scripts": {
"cypress:open": "cypress open",
"cypress:run": "cypress run"
},
"dependencies": {
"@cypress/snapshot": "^2.1.3",
"@cypress/webpack-preprocessor": "^4.1.0",
"@types/js-yaml": "^3.12.1",
"cypress": "^3.4.1",
"js-yaml": "^3.13.1",
"p-limit": "^2.2.1",
"ts-loader": "^6.1.0",
"typescript": "^3.6.3",
"webpack": "^4.40.2"
}
}

View file

@ -0,0 +1,54 @@
/*
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
* or more contributor license agreements. Licensed under the Elastic License;
* you may not use this file except in compliance with the Elastic License.
*/
// ***********************************************************
// This example plugins/index.js can be used to load plugins
//
// You can change the location of this file or turn off loading
// the plugins file with the 'pluginsFile' configuration option.
//
// You can read more here:
// https://on.cypress.io/plugins-guide
// ***********************************************************
// This function is called when a project is opened or re-opened (e.g. due to
// the project's config changing)
// eslint-disable-next-line import/no-extraneous-dependencies
const wp = require('@cypress/webpack-preprocessor');
const fs = require('fs');
module.exports = on => {
// add typescript support
const options = {
webpackOptions: {
resolve: {
extensions: ['.ts', '.tsx', '.js']
},
module: {
rules: [
{
test: /\.tsx?$/,
loader: 'ts-loader',
options: { transpileOnly: true }
}
]
}
}
};
on('file:preprocessor', wp(options));
// readFileMaybe
on('task', {
readFileMaybe(filename) {
if (fs.existsSync(filename)) {
return fs.readFileSync(filename, 'utf8');
}
return null;
}
});
};

View file

@ -0,0 +1 @@
*

View file

@ -0,0 +1,3 @@
// auto-generated by @cypress/snapshot
{
}

View file

@ -0,0 +1,10 @@
/*
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
* or more contributor license agreements. Licensed under the Elastic License;
* you may not use this file except in compliance with the Elastic License.
*/
// @ts-ignore
import { register } from '@cypress/snapshot';
register();

View file

@ -0,0 +1,8 @@
{
"extends": "../../../../tsconfig.json",
"exclude": [],
"include": ["./**/*"],
"compilerOptions": {
"types": ["cypress", "node"]
}
}

File diff suppressed because it is too large Load diff

View file

@ -156,7 +156,7 @@ export class TransactionCharts extends Component<TransactionChartProps> {
return (
<>
<EuiFlexGrid columns={2} gutterSize="s">
<EuiFlexItem>
<EuiFlexItem data-cy={`transaction-duration-charts`}>
<EuiPanel>
<React.Fragment>
<EuiFlexGroup justifyContent="spaceBetween">

View file

@ -1,32 +1,67 @@
# Documentation for APM UI
# Documentation for APM UI developers
### Setup local environment
#### Kibana
```
git clone git@github.com:elastic/kibana.git
cd kibana/
yarn kbn bootstrap
yarn start
```
#### APM Server, Elasticsearch and data
To access an elasticsearch instance that has live data you have two options:
##### A. Connect to Elasticsearch on Cloud (internal devs only)
Add the following to the kibana config file (config/kibana.dev.yml):
https://p.elstc.co/paste/fqorvbJi#Yf6tQ8Bxk4nYMWpoPXr1iZ-QnJ1EbKBEM+H/kdPsmBg
##### B. Start Elastic Stack and APM data generators
```
git clone git@github.com:elastic/apm-integration-testing.git
cd apm-integration-testing/
./scripts/compose.py start master --all --no-kibana
```
_Docker Compose is required_
### Unit testing
Note: Run the following commands from `kibana/x-pack`.
### Run tests
#### Run unit tests
```
node scripts/jest.js plugins/apm --watch
```
### Update snapshots
#### Update snapshots
```
node scripts/jest.js plugins/apm --updateSnapshot
```
---
Note: Run the following commands from `kibana/`.
### Cypress E2E tests
### Prettier
See the Cypress-specific [readme.md](cypress/README.md)
### Linting
_Note: Run the following commands from `kibana/`._
#### Prettier
```
yarn prettier "./x-pack/legacy/plugins/apm/**/*.{tsx,ts,js}" --write
```
### ESLint
#### ESLint
```
yarn eslint ./x-pack/legacy/plugins/apm --fix
```
### Ensure everything from master has been backported to 6.x
```
git fetch origin && git checkout 6.x && git diff origin/6.x..origin/master ./plugins/apm | git apply
```

View file

@ -11,6 +11,7 @@
"exclude": [
"test/**/*",
"legacy/plugins/siem/cypress/**/*",
"legacy/plugins/apm/cypress/**/*",
"**/typespec_tests.ts"
],
"compilerOptions": {