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

Ruchi/cypress lighthouse comp #5

Open
wants to merge 9 commits into
base: main
Choose a base branch
from
Open
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
23 changes: 22 additions & 1 deletion .github/workflows/cypress_workflow.yml
Original file line number Diff line number Diff line change
Expand Up @@ -92,7 +92,7 @@ jobs:
# Dashboard tests with query enhanced - group 3
- group: 13
config: query_enhanced
test_location: source
test_location: source
container:
image: docker://opensearchstaging/ci-runner:ci-runner-rockylinux8-opensearch-dashboards-integtest-v2
options: --user 1001
Expand Down Expand Up @@ -266,6 +266,12 @@ jobs:
wait-on: 'http://localhost:9200, http://localhost:5601'
command: yarn cypress:run-without-security --browser ${{ env.CYPRESS_BROWSER }} --spec ${{ env.DASHBOARDS_SPEC }}

- name: Read Performance Metrics
id: read-metrics
run: |
echo "PERFORMANCE_MESSAGE=$(if [ -f ./cypress/performance_metrics.json ]; then jq -r 'to_entries | map("• ❌ **\(.key)**: \(.value)") | join("\n")' ./cypress/performance_metrics.json; else echo "No performance issues detected 🎉"; fi)" >> $GITHUB_ENV
if [ -f ./cypress/performance_metrics.json ]; then rm ./cypress/performance_metrics.json; fi

# Screenshots are only captured on failure, will change this once we do visual regression tests
- name: Upload FT repo screenshots
uses: actions/upload-artifact@v4
Expand Down Expand Up @@ -315,6 +321,21 @@ jobs:
path: cypress/results
retention-days: 1

- name: Post Performance Comment on PR
uses: peter-evans/create-or-update-comment@v3
if: ${{ always() && inputs.pr_number != '' }}
with:
issue-number: ${{ inputs.pr_number }}
body: |
🚀 **Component Performance Metrics**

The following performance metrics exceeded their thresholds:

${{ env.PERFORMANCE_MESSAGE }}

🔍 **Please review and optimize if necessary.**
reactions: 'eyes,rocket'

add-comment:
needs: [cypress-tests]
if: ${{ always() && github.event_name == 'workflow_dispatch' && inputs.pr_number != '' }}
Expand Down
11 changes: 9 additions & 2 deletions config/opensearch_dashboards.yml
Original file line number Diff line number Diff line change
Expand Up @@ -372,10 +372,17 @@

# Set the backend roles in groups or users, whoever has the backend roles or exactly match the user ids defined in this config will be regard as dashboard admin.
# Dashboard admin will have the access to all the workspaces(workspace.enabled: true) and objects inside OpenSearch Dashboards.
# The default config is [], and no one will be dashboard admin.
# The default config is [], and no one will be dashboard admin.
# If the user config is set to wildcard ["*"], anyone will be dashboard admin.
# opensearchDashboards.dashboardAdmin.groups: ["dashboard_admin"]
# opensearchDashboards.dashboardAdmin.users: ["dashboard_admin"]

# Set the value to true to enable the new UI for savedQueries in Discover
# data.savedQueriesNewUI.enabled: true
# data.savedQueriesNewUI.enabled: true

opensearch.ignoreVersionMismatch: true
data_source.enabled: true
workspace.enabled: true
data:
savedQueriesNewUI:
enabled: true
44 changes: 43 additions & 1 deletion cypress.config.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,9 +2,11 @@
* Copyright OpenSearch Contributors
* SPDX-License-Identifier: Apache-2.0
*/

import fs from 'fs';
import path from 'path';
import { defineConfig } from 'cypress';
import webpackPreprocessor from '@cypress/webpack-preprocessor';
import { prepareAudit } from '@cypress-audit/lighthouse';

module.exports = defineConfig({
defaultCommandTimeout: 60000,
Expand Down Expand Up @@ -77,5 +79,45 @@ function setupNodeEvents(
})
);

on('before:browser:launch', (browser = Cypress.browser, launchOptions) => {
prepareAudit(launchOptions);
});

on('task', {
logPerformance({ metric, value }) {
const dirPath = './cypress';
const filePath = path.join(dirPath, 'performance_metrics.json');

let metrics = {};

// Ensure the directory exists
if (!fs.existsSync(dirPath)) {
fs.mkdirSync(dirPath, { recursive: true });
}

// Ensure that filePath is not a directory
if (fs.existsSync(filePath) && fs.statSync(filePath).isDirectory()) {
// console.error(`Error: ${filePath} is a directory! Removing it...`);
fs.rmdirSync(filePath, { recursive: true }); // Remove incorrect directory
}

// If file exists, read existing metrics
if (fs.existsSync(filePath)) {
try {
const fileContent = fs.readFileSync(filePath, 'utf8');
metrics = fileContent ? JSON.parse(fileContent) : {};
} catch (error) {
// console.error('Error reading metrics file:', error);
}
}

// Add new metric
metrics[metric] = value;
fs.writeFileSync(filePath, JSON.stringify(metrics, null, 2));

return null;
},
});

return config;
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,57 @@
/*
* Copyright OpenSearch Contributors
* SPDX-License-Identifier: Apache-2.0
*/

import {
DATASOURCE_NAME,
INDEX_WITH_TIME_1,
INDEX_WITH_TIME_2,
PATHS,
} from '../../../../../utils/constants';
import { getRandomizedWorkspaceName } from '../../../../../utils/apps/query_enhancements/shared';

import { prepareTestSuite } from '../../../../../utils/helpers';

const workspaceName = getRandomizedWorkspaceName();

export const runSavedSearchTests = () => {
describe('Performance testing', () => {
beforeEach(() => {
// Load test data
cy.osd.setupTestData(
PATHS.SECONDARY_ENGINE,
[
`cypress/fixtures/query_enhancements/data_logs_1/${INDEX_WITH_TIME_1}.mapping.json`,
`cypress/fixtures/query_enhancements/data_logs_1/${INDEX_WITH_TIME_2}.mapping.json`,
],
[
`cypress/fixtures/query_enhancements/data_logs_1/${INDEX_WITH_TIME_1}.data.ndjson`,
`cypress/fixtures/query_enhancements/data_logs_1/${INDEX_WITH_TIME_2}.data.ndjson`,
]
);
cy.osd.deleteDataSourceByName(DATASOURCE_NAME);
// Add data source
cy.osd.addDataSource({
name: DATASOURCE_NAME,
url: PATHS.SECONDARY_ENGINE,
authType: 'no_auth',
});
cy.visit('/app/home');
});

afterEach(() => {
// No need to explicitly delete all saved queries as deleting the workspace will delete associated saved queries
cy.deleteWorkspaceByName(workspaceName);
cy.osd.deleteDataSourceByName(DATASOURCE_NAME);
});

it('should test discover page compoonent performance', () => {
cy.visit('/app/discover');
cy.measureComponentPerformance('discover', 'sidebarPanel');
cy.getElementByTestId('sidebarPanel').should('be.visible');
});
});
};

prepareTestSuite('Performance testing', runSavedSearchTests);
18 changes: 18 additions & 0 deletions cypress/plugins/index.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
/*
* Copyright OpenSearch Contributors
* SPDX-License-Identifier: Apache-2.0
*/

const { lighthouse, prepareAudit } = require('@cypress-audit/lighthouse');

module.exports = (on, config) => {
on('before:browser:launch', (_, launchOptions) => {
prepareAudit(launchOptions);
});

on('task', {
lighthouse: lighthouse(),
});

return config;
};
1 change: 1 addition & 0 deletions cypress/support/e2e.js
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ import '../utils/commands.osd';
import '../utils/apps/commands';
import '../utils/dashboards/workspace-plugin/commands';
import '../utils/dashboards/commands';
import '@cypress-audit/lighthouse/commands';

// TODO: Remove this after https://github.com/opensearch-project/OpenSearch-Dashboards/issues/5476 is resolved
const scopedHistoryNavigationError = /^[^(ScopedHistory instance has fell out of navigation scope)]/;
Expand Down
81 changes: 81 additions & 0 deletions cypress/utils/commands.js
Original file line number Diff line number Diff line change
Expand Up @@ -61,3 +61,84 @@ Cypress.Commands.add('openWorkspaceDashboard', (workspaceName) => {
.find('a.euiLink')
.click();
});

// Command to measure component performance and compare with baseline
// Example - cy.measureComponentPerformance(
// 'workspace-initial-card-createWorkspace-button',
// 'table_render_time'
// );
Cypress.Commands.add('measureComponentPerformance', (pluginName, testId) => {
cy.readFile('cypress/utils/performance_baselines.json').then((baselines) => {
// Retrieve baseline for the given component (testId)
const fieldName = `${pluginName}_${testId}`;
const baseline = baselines[fieldName];

if (baseline) {
cy.window().then((win) => {
const startTime = win.performance.now();

// Measure render time
cy.getElementByTestId(testId)
.should('be.visible')
.then(() => {
const endTime = win.performance.now();
const renderTime = endTime - startTime;

cy.log(renderTime, baseline.render_time, 'renderTime baseline.render_time');
// Compare Render Time with Baseline
if (renderTime > baseline.render_time) {
cy.task('logPerformance', {
metric: `${fieldName}_render_time`,
value: `exceeded: ${renderTime.toFixed(2)}ms > ${baseline.render_time}ms`,
});
}

// Capture Layout Shift (CLS)
cy.window().then((win) => {
return new Cypress.Promise((resolve) => {
let layoutShiftScore = 0;
const observer = new win.PerformanceObserver((list) => {
for (const entry of list.getEntries()) {
if (!entry.hadRecentInput) {
layoutShiftScore += entry.value;
}
}
});
observer.observe({ type: 'layout-shift', buffered: true });

setTimeout(() => {
observer.disconnect();
resolve(layoutShiftScore);
}, 2000);
}).then((layoutShiftScore) => {
// Compare Layout Shift (CLS) with Baseline
if (layoutShiftScore > baseline.layout_shift) {
cy.task('logPerformance', {
metric: `${fieldName}_layout_shift`,
value: `exceeded: ${layoutShiftScore.toFixed(2)} > ${
baseline.layout_shift
} (CLS)`,
});
}
});
});

// Capture Memory Usage
cy.window().then((win) => {
const memoryUsage = win.performance.memory.usedJSHeapSize / 1024 / 1024;

// Compare Memory Usage with Baseline
if (memoryUsage > baseline.memory_MB) {
cy.task('logPerformance', {
metric: `${fieldName}_memory_MB`,
value: `exceeded: ${memoryUsage.toFixed(2)}MB > ${baseline.memory_MB}MB`,
});
}
});
});
});
} else {
cy.log(`No baseline found for component: ${fieldName}`);
}
});
});
10 changes: 10 additions & 0 deletions cypress/utils/lighthouse_baselines.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
{
"discover": {
"performance": 30,
"accessibility": 50
},
"dashboard": {
"performance": 30,
"accessibility": 50
}
}
12 changes: 12 additions & 0 deletions cypress/utils/performance_baselines.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
{
"discover_docTable": {
"render_time": 800,
"layout_shift": 0.08,
"memory_MB": 40
},
"discover_sidebarPanel": {
"render_time": 500,
"layout_shift": 0.08,
"memory_MB": 40
}
}
1 change: 1 addition & 0 deletions docs/_sidebar.md
Original file line number Diff line number Diff line change
Expand Up @@ -196,4 +196,5 @@
- [RELEASING](../RELEASING.md)
- [SECURITY](../SECURITY.md)
- [TESTING](../TESTING.md)
- [TRIAGING](../TRIAGING.md)
- [TYPESCRIPT](../TYPESCRIPT.md)
1 change: 1 addition & 0 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -263,6 +263,7 @@
"@babel/plugin-transform-class-static-block": "^7.24.4",
"@babel/register": "^7.22.9",
"@babel/types": "^7.22.9",
"@cypress-audit/lighthouse": "^1.4.2",
"@cypress/webpack-preprocessor": "^5.17.1",
"@elastic/apm-rum": "^5.6.1",
"@elastic/charts": "31.1.0",
Expand Down
Loading
Loading