Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add visual regression testing library + tests #762

Closed
wants to merge 5 commits into from
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 2 additions & 1 deletion .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -6,4 +6,5 @@ cypress/screenshots/
**/.DS_Store
.idea

/.opensearch
/.opensearch
**/__diff_output__/
23 changes: 19 additions & 4 deletions DEVELOPER_GUIDE.md
Original file line number Diff line number Diff line change
Expand Up @@ -27,7 +27,7 @@ You should have a running instance of OpenSearch Dashboards to run these tests a

### Installation

To install the dependencies run
To install the dependencies run

```
npm install
Expand Down Expand Up @@ -85,7 +85,7 @@ with security:
$ yarn cypress run-with-security --spec "cypress/integration/core-opensearch-dashboards/opensearch-dashboards/*.js"
```

These tests run in headless mode by default.
These tests run in headless mode by default.

And you can override certain [cypress config or environment variable](cypress.json) by applying additional cli arguments, for example to override the baseUrl and openSearchUrl to test a remote OpenSearch endpoint:

Expand All @@ -99,7 +99,6 @@ $ yarn cypress run --spec "cypress/integration/core-opensearch-dashboards/opense

`MANAGED_SERVICE_ENDPOINT`: set to true if tests are running against managed service domains.


## Writing tests

The testing library uses [Cypress](https://www.cypress.io/) as its testing framework and follow its high level folder structure. All tests are written under the `./cypress/integration` folder.
Expand All @@ -125,6 +124,19 @@ Tests for plugins that are not a part of the [OpenSearch Dashboards](https://git
/plugins
/<YOUR_PLUGIN_NAME>
```

### Tests with snapshots

[cypress-image-snapshot](https://github.com/jaredpalmer/cypress-image-snapshot) is a visual regression testing framework that can be used to persist snapshots & compare against them during test runs. This is very useful for checking elements that may not allow for easy validation via HTML elements, such as Vega visualizations which are a single black-box `canvas` element.

Usage:

```
cy.get('<selector>').matchImageSnapshot('<snapshot-name>')
```

You use the same `matchImageSnapshot()` command to take new snapshots, or compare against existing ones. You can pass the flag `--env updateSnapshots=true` into the command to create/update them. They will be stored in the `snapshots/` directory. To do comparisons against existing snapshots later on, simply run without the flag. For further details, see the [GitHub repository](https://github.com/jaredpalmer/cypress-image-snapshot).

### Experimental Features

When writing tests for experimental features, please follow these steps.
Expand All @@ -146,16 +158,19 @@ Create a new workflow by referring to [this template](https://github.com/opensea
To make the build repo enable your experimental feature when spinning up OSD service, make sure that you update [this file](https://github.com/opensearch-project/opensearch-build/blob/main/src/test_workflow/integ_test/service_opensearch_dashboards.py) You could either modify the start command or the yml file. To avoid a potentially long start command, it is preferred to modify the yml file to turn on the feature.

## General

### Formatting

`prettier` and `ESLint` is integrated and used to standardize formatting of files, where `prettier` takes care of the code formatting and `ESLint` takes care of the code style. You can check the formatting of all files (new and existing) by running

```
$ yarn lint
```

and auto fix the formatting of all files (new and existing) by running

```
$ yarn lint --fix
```

`Husky` precommit hook is used to automatically run `yarn lint`, please fix the files according to lint result before commiting code changes (run `yarn lint --fix` for fixable errors, or manully fix code according to error messages). If you have any doubts on `ESLint` rules, feel free to [open an issue](issues).
`Husky` precommit hook is used to automatically run `yarn lint`, please fix the files according to lint result before commiting code changes (run `yarn lint --fix` for fixable errors, or manully fix code according to error messages). If you have any doubts on `ESLint` rules, feel free to [open an issue](issues).
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,8 @@ import {
import {
deleteVisAugmenterData,
bootstrapDashboard,
validateVisSnapshot,
setDateRangeTo7Days,
} from '../../../../../utils/dashboards/vis-augmenter/helpers';

describe('Vis augmenter - existing dashboards work as expected', () => {
Expand Down Expand Up @@ -89,6 +91,8 @@ describe('Vis augmenter - existing dashboards work as expected', () => {
dashboardName,
visualizationSpecs
);
// Setting viewport so the snapshots in the tests are consistent
cy.viewport(1280, 720);
});

beforeEach(() => {
Expand All @@ -105,12 +109,25 @@ describe('Vis augmenter - existing dashboards work as expected', () => {
});

it('View events option does not exist for any visualization', () => {
// Change date range to 7 days so there is less variability in charts
// which can cause flakiness (e.g., chart snapshot comparisons).
setDateRangeTo7Days();

visualizationNames.forEach((visualizationName) => {
// Validating after making each vis full-screen. This is because if there
// is a lot of visualizations on the screen, not all may be visible at the
// same time on the dashboard - some may require scrolling to be in view.
validateVisSnapshot(
visualizationName,
`${visualizationName}-last-7-days`,
true
);
cy.getVisPanelByTitle(visualizationName)
.openVisContextMenu()
.getMenuItems()
.contains('View Events')
.should('not.exist');
cy.getVisPanelByTitle(visualizationName).closeVisContextMenu();
});
});

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,8 @@ import {
ensureDetectorIsLinked,
ensureDetectorDetails,
openDetectorDetailsPageFromFlyout,
validateVisSnapshot,
setDateRangeTo7Days,
} from '../../../../utils/helpers';
import {
INDEX_PATTERN_FILEPATH_SIMPLE,
Expand Down Expand Up @@ -51,6 +53,8 @@ describe('Anomaly detection integration with vis augmenter', () => {
dashboardName,
[visualizationSpec]
);
// Setting viewport so the snapshots in the tests are consistent
cy.viewport(1280, 720);
});

after(() => {
Expand Down Expand Up @@ -82,6 +86,13 @@ describe('Anomaly detection integration with vis augmenter', () => {
// the number of features will equal the number of metrics we have specified.
ensureDetectorDetails(detectorName, visualizationSpec.metrics.length);

cy.visitDashboard(dashboardName);
setDateRangeTo7Days();
validateVisSnapshot(
visualizationName,
`${visualizationName}-with-created-detector`
);

unlinkDetectorFromVis(dashboardName, visualizationName, detectorName);
});

Expand All @@ -96,6 +107,14 @@ describe('Anomaly detection integration with vis augmenter', () => {
associateDetectorFromVis(detectorName);

ensureDetectorIsLinked(dashboardName, visualizationName, detectorName);

cy.visitDashboard(dashboardName);
setDateRangeTo7Days();
validateVisSnapshot(
visualizationName,
`${visualizationName}-with-associated-detector`
);

unlinkDetectorFromVis(dashboardName, visualizationName, detectorName);
});

Expand All @@ -105,6 +124,14 @@ describe('Anomaly detection integration with vis augmenter', () => {
associateDetectorFromVis(detectorName);

ensureDetectorIsLinked(dashboardName, visualizationName, detectorName);

cy.visitDashboard(dashboardName);
setDateRangeTo7Days();
validateVisSnapshot(
visualizationName,
`${visualizationName}-with-associated-detector-2`
);

unlinkDetectorFromVis(dashboardName, visualizationName, detectorName);
});

Expand Down
5 changes: 5 additions & 0 deletions cypress/plugins/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,10 @@
* SPDX-License-Identifier: Apache-2.0
*/

const {
addMatchImageSnapshotPlugin,
} = require('cypress-image-snapshot/plugin');

/// <reference types="cypress" />
// ***********************************************************
// This example plugins/index.js can be used to load plugins
Expand All @@ -24,4 +28,5 @@
module.exports = (on, config) => {
// `on` is used to hook into various events Cypress emits
// `config` is the resolved Cypress config
addMatchImageSnapshotPlugin(on, config);
};
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
12 changes: 12 additions & 0 deletions cypress/support/commands.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
/*
* Copyright OpenSearch Contributors
* SPDX-License-Identifier: Apache-2.0
*/

import { addMatchImageSnapshotCommand } from 'cypress-image-snapshot/command';

addMatchImageSnapshotCommand({
failureThreshold: 0.03, // threshold for entire image
failureThresholdType: 'percent', // percent of image or number of pixels
capture: 'viewport', // capture viewport in screenshot
});
1 change: 1 addition & 0 deletions cypress/support/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@
// ***********************************************************

// Import commands.js using ES2015 syntax:
import './commands';
import '../utils/commands';
import '../utils/dashboards/commands';
import '../utils/dashboards/datasource-management-dashboards-plugin/commands';
Expand Down
7 changes: 7 additions & 0 deletions cypress/utils/dashboards/vis-augmenter/commands.js
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,13 @@ Cypress.Commands.add('openVisContextMenu', { prevSubject: true }, (panel) =>
.then(() => cy.get('.euiContextMenu'))
);

Cypress.Commands.add('closeVisContextMenu', { prevSubject: true }, (panel) =>
cy
.wrap(panel)
.find(`[data-test-subj="embeddablePanelContextMenuOpen"]`)
.click()
);

Cypress.Commands.add(
'clickVisPanelMenuItem',
{ prevSubject: 'optional' },
Expand Down
32 changes: 32 additions & 0 deletions cypress/utils/dashboards/vis-augmenter/helpers.js
Original file line number Diff line number Diff line change
Expand Up @@ -320,3 +320,35 @@ export const filterByObjectType = (type) => {
.click({ force: true });
cy.wait(3000);
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

not related to this PR but you guys have a lot of wait(time in ms) statements all across your tests. This is almost always unnecessary and can save you guys a lot of time in your tests.

};

// Assumes runner is on dashboard
export const validateVisSnapshot = (
visualizationName,
snapshotName,
maximizePanel = false
) => {
if (maximizePanel) {
cy.getVisPanelByTitle(visualizationName)
.openVisContextMenu()
.clickVisPanelMenuItem('Maximize panel');
cy.wait(1000);
cy.get(`[data-title="${visualizationName}"]`).matchImageSnapshot(
snapshotName
);
cy.getVisPanelByTitle(visualizationName)
.openVisContextMenu()
.clickVisPanelMenuItem('Minimize');
} else {
cy.get(`[data-title="${visualizationName}"]`).matchImageSnapshot(
snapshotName
);
}
};

// Assumes runner is on dashboard
export const setDateRangeTo7Days = () => {
cy.getElementByTestId('superDatePickerToggleQuickMenuButton').click();
cy.getElementByTestId('superDatePickerCommonlyUsed_Last_7 days').click();
cy.getElementByTestId('querySubmitButton').click();
cy.wait(2000);
};
Loading
Loading