Skip to content

Commit 4498da5

Browse files
committed
cypress tests set up with e2e tests for all pages
Signed-off-by: Chenyang Ji <[email protected]>
1 parent 3ceb453 commit 4498da5

17 files changed

+5591
-197
lines changed

.eslintrc.js

+10-1
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,7 @@ module.exports = {
1313
extends: [
1414
'@elastic/eslint-config-kibana',
1515
'plugin:@elastic/eui/recommended',
16+
"plugin:cypress/recommended",
1617
'plugin:react-hooks/recommended',
1718
'plugin:jest/recommended',
1819
'plugin:prettier/recommended',
@@ -38,6 +39,7 @@ module.exports = {
3839
],
3940
},
4041
],
42+
'cypress/no-unnecessary-waiting': 'off',
4143
},
4244
overrides: [
4345
{
@@ -53,6 +55,13 @@ module.exports = {
5355
],
5456
},
5557
},
58+
{
59+
'files': [ '**/*.cy.js' ],
60+
"rules": {
61+
'jest/valid-expect': 'off',
62+
'cypress/unsafe-to-chain-command': 'off',
63+
}
64+
},
5665
],
57-
"ignorePatterns": ["**/*.d.ts"]
66+
'ignorePatterns': ['**/*.d.ts']
5867
};

.github/workflows/cypress-tests.yml

+155
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,155 @@
1+
name: Cypress e2e integration tests workflow
2+
on:
3+
pull_request:
4+
branches:
5+
- "*"
6+
push:
7+
branches:
8+
- "*"
9+
env:
10+
OPENSEARCH_DASHBOARDS_VERSION: 'main'
11+
OPENSEARCH_VERSION: '3.0.0-SNAPSHOT'
12+
QUERY_INSIGHTS_BRANCH: 'main'
13+
GRADLE_VERSION: '7.6.1'
14+
jobs:
15+
tests:
16+
name: Run Cypress E2E tests
17+
strategy:
18+
matrix:
19+
os: [ubuntu-latest, windows-latest]
20+
include:
21+
- os: windows-latest
22+
cypress_cache_folder: ~/AppData/Local/Cypress/Cache
23+
- os: ubuntu-latest
24+
cypress_cache_folder: ~/.cache/Cypress
25+
runs-on: ${{ matrix.os }}
26+
env:
27+
# prevents extra Cypress installation progress messages
28+
CI: 1
29+
# avoid warnings like "tput: No value for $TERM and no -T specified"
30+
TERM: xterm
31+
steps:
32+
- name: Set up JDK
33+
uses: actions/setup-java@v1
34+
with:
35+
java-version: 21
36+
37+
- name: Enable longer filenames
38+
if: ${{ matrix.os == 'windows-latest' }}
39+
run: git config --system core.longpaths true
40+
41+
- name: Checkout Query Insights
42+
uses: actions/checkout@v2
43+
with:
44+
path: query-insights
45+
repository: opensearch-project/query-insights
46+
ref: ${{ env.QUERY_INSIGHTS_BRANCH }}
47+
48+
- name: Set up Gradle
49+
uses: gradle/gradle-build-action@v2
50+
with:
51+
gradle-version: ${{ env.GRADLE_VERSION }}
52+
53+
- name: Run OpenSearch with Query Insights plugin
54+
run: |
55+
cd query-insights
56+
./gradlew run -Dopensearch.version=${{ env.OPENSEARCH_VERSION }} &
57+
sleep 300
58+
shell: bash
59+
60+
- name: Checkout OpenSearch-Dashboards
61+
uses: actions/checkout@v2
62+
with:
63+
repository: opensearch-project/OpenSearch-Dashboards
64+
path: OpenSearch-Dashboards
65+
ref: ${{ env.OPENSEARCH_DASHBOARDS_VERSION }}
66+
67+
- name: Checkout Query Insights Dashboards plugin
68+
uses: actions/checkout@v2
69+
with:
70+
path: OpenSearch-Dashboards/plugins/query-insights-dashboards
71+
72+
- name: Setup Node
73+
uses: actions/setup-node@v3
74+
with:
75+
node-version-file: './OpenSearch-Dashboards/.nvmrc'
76+
registry-url: 'https://registry.npmjs.org'
77+
78+
- name: Install Yarn
79+
# Need to use bash to avoid having a windows/linux specific step
80+
shell: bash
81+
run: |
82+
YARN_VERSION=$(node -p "require('./OpenSearch-Dashboards/package.json').engines.yarn")
83+
echo "Installing yarn@$YARN_VERSION"
84+
npm i -g yarn@$YARN_VERSION
85+
- run: node -v
86+
- run: yarn -v
87+
88+
- name: Bootstrap plugin/OpenSearch-Dashboards
89+
run: |
90+
cd OpenSearch-Dashboards/plugins/query-insights-dashboards
91+
yarn osd bootstrap --single-version=loose
92+
93+
- name: Run OpenSearch-Dashboards server
94+
run: |
95+
cd OpenSearch-Dashboards
96+
yarn start --no-base-path --no-watch --server.host="0.0.0.0" &
97+
shell: bash
98+
99+
# Window is slow so wait longer
100+
- name: Sleep until OSD server starts - windows
101+
if: ${{ matrix.os == 'windows-latest' }}
102+
run: Start-Sleep -s 600
103+
shell: powershell
104+
105+
- name: Sleep until OSD server starts - non-windows
106+
if: ${{ matrix.os != 'windows-latest' }}
107+
run: sleep 450
108+
shell: bash
109+
110+
- name: Install Cypress
111+
run: |
112+
cd OpenSearch-Dashboards/plugins/query-insights-dashboards
113+
# This will install Cypress in case the binary is missing which can happen on Windows and Mac
114+
# If the binary exists, this will exit quickly so it should not be an expensive operation
115+
npx cypress install
116+
shell: bash
117+
118+
- name: Get Cypress version
119+
id: cypress_version
120+
run: |
121+
cd OpenSearch-Dashboards/plugins/query-insights-dashboards
122+
echo "::set-output name=cypress_version::$(cat ./package.json | jq '.dependencies.cypress' | tr -d '"')"
123+
124+
- name: Cache Cypress
125+
id: cache-cypress
126+
uses: actions/cache@v2
127+
with:
128+
path: ${{ matrix.cypress_cache_folder }}
129+
key: cypress-cache-v2-${{ matrix.os }}-${{ hashFiles('OpenSearch-Dashboards/plugins/query-insights-dashboards/package.json') }}
130+
131+
# for now just chrome, use matrix to do all browsers later
132+
- name: Cypress tests
133+
uses: cypress-io/github-action@v5
134+
with:
135+
working-directory: OpenSearch-Dashboards/plugins/query-insights-dashboards
136+
command: yarn run cypress run
137+
wait-on: 'http://localhost:5601'
138+
wait-on-timeout: 300
139+
browser: chrome
140+
env:
141+
CYPRESS_CACHE_FOLDER: ${{ matrix.cypress_cache_folder }}
142+
143+
# Screenshots are only captured on failure, will change this once we do visual regression tests
144+
- uses: actions/upload-artifact@v4
145+
if: failure()
146+
with:
147+
name: cypress-screenshots-${{ matrix.os }}
148+
path: OpenSearch-Dashboards/plugins/query-insights-dashboards/cypress/screenshots
149+
150+
# Test run video was always captured, so this action uses "always()" condition
151+
- uses: actions/upload-artifact@v4
152+
if: always()
153+
with:
154+
name: cypress-videos-${{ matrix.os }}
155+
path: OpenSearch-Dashboards/plugins/query-insights-dashboards/cypress/videos

cypress.config.js

+23
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,23 @@
1+
const { defineConfig } = require('cypress')
2+
3+
module.exports = defineConfig({
4+
video: false,
5+
screenshotOnRunFailure: false,
6+
defaultCommandTimeout: 60000,
7+
requestTimeout: 600000,
8+
responseTimeout: 600000,
9+
env: {
10+
openSearchUrl: 'http://localhost:9200',
11+
SECURITY_ENABLED: false,
12+
username: 'admin',
13+
password: 'admin',
14+
},
15+
e2e: {
16+
// We've imported your old cypress plugins here.
17+
// You may want to clean this up later by importing these.
18+
setupNodeEvents(on, config) {
19+
return require('./cypress/plugins/index.js')(on, config)
20+
},
21+
baseUrl: 'http://localhost:5601',
22+
},
23+
})

cypress/e2e/1_top_queries.cy.js

+139
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,139 @@
1+
/*
2+
* Copyright OpenSearch Contributors
3+
* SPDX-License-Identifier: Apache-2.0
4+
*/
5+
6+
import sampleDocument from '../fixtures/sample_document.json';
7+
import { METRICS } from '../support/constants';
8+
9+
// Name of the test index used in tests
10+
const indexName = 'sample_index';
11+
12+
/**
13+
Helper function to clean up the environment:
14+
- Deletes the test index.
15+
- Disables the top queries features.
16+
*/
17+
const clearAll = () => {
18+
cy.deleteIndexByName(indexName);
19+
cy.disableTopQueries(METRICS.LATENCY);
20+
cy.disableTopQueries(METRICS.CPU);
21+
cy.disableTopQueries(METRICS.MEMORY);
22+
};
23+
24+
describe('Query Insights Dashboard', () => {
25+
// Setup before each test
26+
beforeEach(() => {
27+
clearAll();
28+
cy.createIndexByName(indexName, sampleDocument);
29+
cy.enableTopQueries(METRICS.LATENCY);
30+
cy.enableTopQueries(METRICS.CPU);
31+
cy.searchOnIndex(indexName);
32+
// wait for 1s to avoid same timestamp
33+
cy.wait(1000);
34+
cy.searchOnIndex(indexName);
35+
cy.wait(1000);
36+
cy.searchOnIndex(indexName);
37+
// waiting for the query insights queue to drain
38+
cy.wait(6000);
39+
cy.navigateToOverview();
40+
});
41+
42+
/**
43+
* Validate the main overview page loads correctly
44+
*/
45+
it('should display the main overview page', () => {
46+
cy.get('.euiBasicTable').should('be.visible');
47+
cy.contains('Query insights - Top N queries');
48+
cy.url().should('include', '/queryInsights');
49+
50+
// should display the query table on the overview page
51+
cy.get('.euiBasicTable').should('be.visible');
52+
cy.get('.euiTableHeaderCell').should('have.length.greaterThan', 0);
53+
// should have top n queries displayed on the table
54+
cy.get('.euiTableRow').should('have.length.greaterThan', 0);
55+
});
56+
57+
/**
58+
* Validate sorting by the "Timestamp" column works correctly
59+
*/
60+
it('should sort the table by the Timestamp column', () => {
61+
// Capture the first row content before sorting
62+
let firstRowBeforeSort;
63+
cy.get('.euiTableRow')
64+
.first()
65+
.invoke('text')
66+
.then((text) => {
67+
firstRowBeforeSort = text.trim();
68+
});
69+
// Click the Timestamp column header to sort
70+
cy.get('.euiTableHeaderCell').contains('Timestamp').click();
71+
// eslint-disable-next-line jest/valid-expect-in-promise
72+
cy.get('.euiTableRow')
73+
.first()
74+
.invoke('text')
75+
.then((firstRowAfterSort) => {
76+
// Assert that the content has changed
77+
expect(firstRowAfterSort.trim()).to.not.equal(firstRowBeforeSort);
78+
});
79+
cy.get('.euiTableHeaderCell').contains('Timestamp').click();
80+
// eslint-disable-next-line jest/valid-expect-in-promise
81+
cy.get('.euiTableRow')
82+
.first()
83+
.invoke('text')
84+
.then((firstRowAfterSecondSort) => {
85+
expect(firstRowAfterSecondSort.trim()).to.not.equal(firstRowBeforeSort);
86+
});
87+
});
88+
89+
/**
90+
* Validate pagination works as expected
91+
*/
92+
it('should paginate the query table', () => {
93+
for (let i = 0; i < 20; i++) {
94+
cy.searchOnIndex(indexName);
95+
}
96+
// waiting for the query insights queue to drain
97+
cy.wait(10000);
98+
cy.reload();
99+
cy.get('.euiPagination').should('be.visible');
100+
cy.get('.euiPagination__item').contains('2').click();
101+
// Verify rows on the second page
102+
cy.get('.euiTableRow').should('have.length.greaterThan', 0);
103+
});
104+
105+
it('should switch between tabs', () => {
106+
// Click Configuration tab
107+
cy.getElementByText('.euiTab', 'Configuration').click({ force: true });
108+
cy.contains('Query insights - Configuration');
109+
cy.url().should('include', '/configuration');
110+
111+
// Click back to Query Insights tab
112+
cy.getElementByText('.euiTab', 'Top N queries').click({ force: true });
113+
cy.url().should('include', '/queryInsights');
114+
});
115+
116+
it('should filter queries', () => {
117+
cy.get('.euiFieldSearch').should('be.visible');
118+
cy.get('.euiFieldSearch').type('sample_index');
119+
// Add assertions for filtered results
120+
cy.get('.euiTableRow').should('have.length.greaterThan', 0);
121+
});
122+
123+
it('should clear the search input and reset results', () => {
124+
cy.get('.euiFieldSearch').type('random_string');
125+
cy.get('.euiTableRow').should('have.length.greaterThan', 0);
126+
cy.get('.euiFieldSearch').clear();
127+
cy.get('.euiTableRow').should('have.length.greaterThan', 0); // Validate reset
128+
});
129+
130+
it('should display a message when no top queries are found', () => {
131+
clearAll(); // disable top n queries
132+
// waiting for the query insights queue to drain
133+
cy.wait(10000);
134+
cy.reload();
135+
cy.contains('No items found');
136+
});
137+
138+
after(() => clearAll());
139+
});

0 commit comments

Comments
 (0)