-
Notifications
You must be signed in to change notification settings - Fork 715
test: e2e test case for report #4977
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
Conversation
WalkthroughThe pull request introduces modifications to the Playwright testing framework and UI testing classes. It updates the workflow to include a new test file, Changes
Possibly related PRs
Suggested labels
Suggested reviewers
Thank you for using CodeRabbit. We offer it for free to the OSS community and would appreciate your support in helping us grow. If you find it useful, would you consider giving us a shout-out on your favorite social media? 🪧 TipsChatThere are 3 ways to chat with CodeRabbit:
Note: Be mindful of the bot's finite context window. It's strongly recommended to break down tasks such as reading entire modules into smaller chunks. For a focused discussion, use review comments to chat about specific files and their changes, instead of using the PR comments. CodeRabbit Commands (Invoked using PR comments)
Other keywords and placeholders
CodeRabbit Configuration File (
|
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Actionable comments posted: 11
🧹 Outside diff range and nitpick comments (13)
tests/ui-testing/pages/loginPage.js (1)
9-10: LGTM! Consider documenting the locator strategy change.The switch to
data-cyattribute is a good practice for test stability. Consider adding a brief comment explaining why this selector strategy was chosen over the role-based approach.- //this.loginButton = this.page.getByRole('button', { name: 'Login' }); + // Using data-cy attribute for better test stability and maintainability this.loginButton = page.locator('[data-cy="login-sign-in"]');tests/ui-testing/playwright-tests/reportsUpdated.spec.js (1)
8-10: Consider removing "Updated" suffix from test suite name.The suffix doesn't add value and might cause confusion. Consider renaming to just "Report test cases".
-test.describe("Report test cases Updated", () => { +test.describe("Report test cases", () => {tests/ui-testing/pages/ingestionPage.js (1)
34-34: Use optional chaining for null safety.The condition can be improved using optional chaining to handle potential null values.
- if (contentType && contentType.includes("application/json")) { + if (contentType?.includes("application/json")) {🧰 Tools
🪛 Biome
[error] 34-34: Change to an optional chain.
Unsafe fix: Change to an optional chain.
(lint/complexity/useOptionalChain)
.github/workflows/playwright.yml (2)
90-94: Consider improving the test file matrix configuration.The test file list is now split across multiple lines, which improves readability. However, to make it more maintainable:
- Consider using a consistent indentation level
- Add commas consistently after each entry
- Consider grouping related test files together (e.g., logs-related, dashboard-related)
Apply this diff to improve the configuration:
testfilename: - ["sanity.spec.js", "alerts.spec.js", "schema.spec.js", "logspage.spec.js", - "logsqueries.spec.js", "logsquickmode.spec.js", "multiselect-stream.spec.js", - "pipeline.spec.js", "dashboardtype.spec.js", - "dashboard.spec.js", "visualize.spec.js", "unflattened.spec.js", - "secondsPrecisionAdded.spec.js","reportsUpdated.spec.js"] + [ + # Core functionality + "sanity.spec.js", + "alerts.spec.js", + "schema.spec.js", + # Logs functionality + "logspage.spec.js", + "logsqueries.spec.js", + "logsquickmode.spec.js", + "multiselect-stream.spec.js", + # Dashboard & Visualization + "dashboardtype.spec.js", + "dashboard.spec.js", + "visualize.spec.js", + # Other features + "pipeline.spec.js", + "unflattened.spec.js", + "secondsPrecisionAdded.spec.js", + "reportsUpdated.spec.js" + ]🧰 Tools
🪛 yamllint
[error] 90-90: trailing spaces
(trailing-spaces)
[warning] 91-91: wrong indentation: expected 11 but found 10
(indentation)
[error] 92-92: trailing spaces
(trailing-spaces)
[warning] 93-93: wrong indentation: expected 11 but found 10
(indentation)
[warning] 94-94: wrong indentation: expected 11 but found 10
(indentation)
[warning] 94-94: too few spaces after comma
(commas)
90-94: Fix formatting issues in the test file matrix.The static analysis tools have identified several formatting issues:
- Remove trailing spaces
- Fix indentation to be consistent (11 spaces)
- Add space after comma in the last entry
Apply this diff to fix the formatting:
testfilename: - ["sanity.spec.js", "alerts.spec.js", "schema.spec.js", "logspage.spec.js", - "logsqueries.spec.js", "logsquickmode.spec.js", "multiselect-stream.spec.js", - "pipeline.spec.js", "dashboardtype.spec.js", - "dashboard.spec.js", "visualize.spec.js", "unflattened.spec.js", - "secondsPrecisionAdded.spec.js","reportsUpdated.spec.js"] + ["sanity.spec.js", "alerts.spec.js", "schema.spec.js", "logspage.spec.js", + "logsqueries.spec.js", "logsquickmode.spec.js", "multiselect-stream.spec.js", + "pipeline.spec.js", "dashboardtype.spec.js", + "dashboard.spec.js", "visualize.spec.js", "unflattened.spec.js", + "secondsPrecisionAdded.spec.js", "reportsUpdated.spec.js"]🧰 Tools
🪛 yamllint
[error] 90-90: trailing spaces
(trailing-spaces)
[warning] 91-91: wrong indentation: expected 11 but found 10
(indentation)
[error] 92-92: trailing spaces
(trailing-spaces)
[warning] 93-93: wrong indentation: expected 11 but found 10
(indentation)
[warning] 94-94: wrong indentation: expected 11 but found 10
(indentation)
[warning] 94-94: too few spaces after comma
(commas)
tests/ui-testing/pages/dashboardPage.js (5)
10-10: Enhance dashboard name readability.Currently,
dashboardNameis set todashboard${Date.now()}, which results in a numerical suffix that's not easily readable. Consider formatting the date to improve clarity, such as using a timestamp or a formatted date string.Apply this diff to enhance readability:
- this.dashboardName = `dashboard${Date.now()}`; + const timestamp = new Date().toISOString().replace(/[:.]/g, '-'); + this.dashboardName = `dashboard-${timestamp}`;
60-60: Handle potential changes in data-test IDs.The data-test ID
'field-list-item-logs-default-k8s_app_instance'is quite specific and may change if underlying data changes. Consider parameterizing or making the selector more robust.Apply this diff to generalize the selector:
- await this.page.locator('[data-test="field-list-item-logs-default-k8s_app_instance"] [data-test="dashboard-add-y-data"]').click(); + await this.page.locator('[data-test^="field-list-item-logs-default"] [data-test="dashboard-add-y-data"]').click();
53-59: Use consistent and descriptive panel names.The panel name
'AutoP'might not be descriptive. For better maintainability and readability, use a name that reflects the panel's purpose.Consider updating the panel name:
- await this.page.locator('[data-test="dashboard-panel-name"]').fill('AutoP'); + await this.page.locator('[data-test="dashboard-panel-name"]').fill('AutoGeneratedPanel');
34-65: Factor out repeated locators and actions into helper methods.The
createDashboard()method contains repetitive code for interacting with page elements. Refactoring these into helper methods can improve code reusability and readability.
10-16: Group locators logically and consider page object best practices.Organize locators related to similar functionalities together. Additionally, consider using getter methods for locators to ensure they are instantiated fresh each time, which can prevent stale element issues.
Example:
- this.dashboardNameInput = page.locator('[data-test="add-dashboard-name"]'); + get dashboardNameInput() { + return this.page.locator('[data-test="add-dashboard-name"]'); + }tests/ui-testing/pages/reportsPage.js (3)
55-55: Avoid using fixed timeouts with 'waitForTimeout'Using
waitForTimeoutwith fixed delays can lead to flaky tests and longer execution times. It's better to wait for specific events or elements to ensure synchronization.Replace
waitForTimeoutwith more reliable synchronization methods. For example:- await this.page.waitForTimeout(5000); + await this.page.waitForLoadState('networkidle');Ensure that you're waiting for the necessary conditions before proceeding.
Also applies to: 63-63, 69-69
54-54: Parameterize hardcoded values in input fieldsHardcoding values like
"rreport1"and"reporterTest"reduces code flexibility. Consider passing these values as parameters or using constants.Refactor the code to accept
reportNameandreportTitleas parameters:- await this.reportNameInput.fill("rreport1"); + await this.reportNameInput.fill(reportName); - await this.titleInput.fill("reporterTest"); + await this.titleInput.fill(reportTitle);Update the method signature accordingly.
Also applies to: 76-76
43-43: Remove commented-out code for clarityThe line
//await this.reportsMenu.click();is commented out. If it's no longer needed, consider removing it to keep the code clean and maintainable.- //await this.reportsMenu.click();If the line is needed for future functionality, add a comment explaining why it's commented out.
📜 Review details
Configuration used: CodeRabbit UI
Review profile: CHILL
📒 Files selected for processing (6)
.github/workflows/playwright.yml(1 hunks)tests/ui-testing/pages/dashboardPage.js(2 hunks)tests/ui-testing/pages/ingestionPage.js(1 hunks)tests/ui-testing/pages/loginPage.js(1 hunks)tests/ui-testing/pages/reportsPage.js(3 hunks)tests/ui-testing/playwright-tests/reportsUpdated.spec.js(1 hunks)
🧰 Additional context used
📓 Path-based instructions (5)
tests/ui-testing/pages/dashboardPage.js (1)
Pattern **/*.js: You are a smart javascript/typescript pull request reviewer.
You are going to review all the javascript/typescript files.
Be concise, and add a brief explanation to your suggestions
Make sure the responses are not too verbose and keep the suggestions to the point i.e. actionable.
tests/ui-testing/pages/ingestionPage.js (1)
Pattern **/*.js: You are a smart javascript/typescript pull request reviewer.
You are going to review all the javascript/typescript files.
Be concise, and add a brief explanation to your suggestions
Make sure the responses are not too verbose and keep the suggestions to the point i.e. actionable.
tests/ui-testing/pages/loginPage.js (1)
Pattern **/*.js: You are a smart javascript/typescript pull request reviewer.
You are going to review all the javascript/typescript files.
Be concise, and add a brief explanation to your suggestions
Make sure the responses are not too verbose and keep the suggestions to the point i.e. actionable.
tests/ui-testing/pages/reportsPage.js (1)
Pattern **/*.js: You are a smart javascript/typescript pull request reviewer.
You are going to review all the javascript/typescript files.
Be concise, and add a brief explanation to your suggestions
Make sure the responses are not too verbose and keep the suggestions to the point i.e. actionable.
tests/ui-testing/playwright-tests/reportsUpdated.spec.js (1)
Pattern **/*.js: You are a smart javascript/typescript pull request reviewer.
You are going to review all the javascript/typescript files.
Be concise, and add a brief explanation to your suggestions
Make sure the responses are not too verbose and keep the suggestions to the point i.e. actionable.
🪛 yamllint
.github/workflows/playwright.yml
[error] 90-90: trailing spaces
(trailing-spaces)
[warning] 91-91: wrong indentation: expected 11 but found 10
(indentation)
[error] 92-92: trailing spaces
(trailing-spaces)
[warning] 93-93: wrong indentation: expected 11 but found 10
(indentation)
[warning] 94-94: wrong indentation: expected 11 but found 10
(indentation)
[warning] 94-94: too few spaces after comma
(commas)
🪛 Biome
tests/ui-testing/pages/ingestionPage.js
[error] 34-34: Change to an optional chain.
Unsafe fix: Change to an optional chain.
(lint/complexity/useOptionalChain)
🔇 Additional comments (7)
tests/ui-testing/playwright-tests/reportsUpdated.spec.js (1)
1-6: LGTM! Clean imports following Playwright conventions.
tests/ui-testing/pages/ingestionPage.js (1)
1-8: LGTM! Clean class structure following page object model pattern.
.github/workflows/playwright.yml (1)
Line range hint 1-150: Workflow structure looks good!
The workflow structure is well-organized with proper job dependencies. The addition of the new test file reportsUpdated.spec.js integrates seamlessly with the existing configuration.
🧰 Tools
🪛 yamllint
[error] 89-89: trailing spaces
(trailing-spaces)
[error] 90-90: trailing spaces
(trailing-spaces)
[warning] 91-91: wrong indentation: expected 11 but found 10
(indentation)
[error] 92-92: trailing spaces
(trailing-spaces)
[warning] 93-93: wrong indentation: expected 11 but found 10
(indentation)
[warning] 94-94: wrong indentation: expected 11 but found 10
(indentation)
[warning] 94-94: too few spaces after comma
(commas)
[error] 95-95: trailing spaces
(trailing-spaces)
tests/ui-testing/pages/dashboardPage.js (1)
Line range hint 23-24: Ensure environment variable is correctly accessed for profileButton.
The expression (process.env["ZO_ROOT_USER_EMAIL"]) might return undefined if the environment variable is not set. Ensure that this variable is defined when running the tests.
Check if ZO_ROOT_USER_EMAIL is set before using it:
#!/bin/bash
# Description: Verify that ZO_ROOT_USER_EMAIL is defined.
# Test: Check for environment variable. Expect: Variable is set.
if [[ -z "${ZO_ROOT_USER_EMAIL}" ]]; then
echo "Environment variable ZO_ROOT_USER_EMAIL is not set."
exit 1
else
echo "ZO_ROOT_USER_EMAIL is set to ${ZO_ROOT_USER_EMAIL}."
fitests/ui-testing/pages/reportsPage.js (3)
27-27: Verify correct alert element is selected
Using getByRole('alert').nth(1) selects the second alert element. Ensure this targets the intended success message, or adjust the index if necessary.
If there's only one alert, you might need to use nth(0):
- this.successAlert = page.getByRole('alert').nth(1);
+ this.successAlert = page.getByRole('alert').nth(0);Confirm that the selector captures the correct alert message.
77-77: Ensure environment variable 'ZO_ROOT_USER_EMAIL' is set
Using process.env["ZO_ROOT_USER_EMAIL"] requires that the environment variable is correctly configured in the test environment.
Confirm that ZO_ROOT_USER_EMAIL is defined and accessible during test execution to avoid undefined values.
88-93: Verify dynamic selector in 'deleteReport' method
Ensure that the dynamic selector correctly targets the delete button for the specified report.
Check that the selector is constructed properly:
`[data-test="report-list-${reportName}-delete-report"]`If the reportName contains spaces or special characters, you might need to adjust the selector accordingly or sanitize the input.
tests/ui-testing/pages/loginPage.js
Outdated
| async login() { | ||
| // Fill in user credentials and login | ||
| await this.userIdInput.fill(username); | ||
| await this.passwordInput.fill(password); | ||
| await this.page.waitForSelector('[data-test="login-user-id"]'); | ||
| await this.userIdInput.fill(process.env["ZO_ROOT_USER_EMAIL"]); | ||
| await this.page.waitForSelector('[data-test="login-password"]'); | ||
| await this.passwordInput.fill(process.env["ZO_ROOT_USER_PASSWORD"]); | ||
| await this.page.waitForSelector('[data-cy="login-sign-in"]'); | ||
| await this.loginButton.click(); | ||
|
|
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
🛠️ Refactor suggestion
Improve error handling and remove redundant waits.
While using environment variables is good practice, there are a few improvements needed:
async login() {
- // Fill in user credentials and login
+ // Verify required environment variables
+ const email = process.env["ZO_ROOT_USER_EMAIL"];
+ const password = process.env["ZO_ROOT_USER_PASSWORD"];
+ if (!email || !password) {
+ throw new Error('Missing required environment variables: ZO_ROOT_USER_EMAIL or ZO_ROOT_USER_PASSWORD');
+ }
- await this.page.waitForSelector('[data-test="login-user-id"]');
- await this.userIdInput.fill(process.env["ZO_ROOT_USER_EMAIL"]);
- await this.page.waitForSelector('[data-test="login-password"]');
- await this.passwordInput.fill(process.env["ZO_ROOT_USER_PASSWORD"]);
- await this.page.waitForSelector('[data-cy="login-sign-in"]');
+ await this.userIdInput.fill(email);
+ await this.passwordInput.fill(password);
await this.loginButton.click();
-
}Changes suggested:
- Add validation for required environment variables
- Remove redundant
waitForSelectorcalls as Playwright'sfill()already includes implicit waiting - Remove trailing empty line
📝 Committable suggestion
‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.
| async login() { | |
| // Fill in user credentials and login | |
| await this.userIdInput.fill(username); | |
| await this.passwordInput.fill(password); | |
| await this.page.waitForSelector('[data-test="login-user-id"]'); | |
| await this.userIdInput.fill(process.env["ZO_ROOT_USER_EMAIL"]); | |
| await this.page.waitForSelector('[data-test="login-password"]'); | |
| await this.passwordInput.fill(process.env["ZO_ROOT_USER_PASSWORD"]); | |
| await this.page.waitForSelector('[data-cy="login-sign-in"]'); | |
| await this.loginButton.click(); | |
| async login() { | |
| // Verify required environment variables | |
| const email = process.env["ZO_ROOT_USER_EMAIL"]; | |
| const password = process.env["ZO_ROOT_USER_PASSWORD"]; | |
| if (!email || !password) { | |
| throw new Error('Missing required environment variables: ZO_ROOT_USER_EMAIL or ZO_ROOT_USER_PASSWORD'); | |
| } | |
| await this.userIdInput.fill(email); | |
| await this.passwordInput.fill(password); | |
| await this.loginButton.click(); | |
| } |
| test.beforeEach(async ({ page }) => { | ||
| loginPage = new LoginPage(page); | ||
| ingestionPage = new IngestionPage(page); | ||
| dashboardPage = new DashboardPage(page); | ||
| reportsPage = new ReportsPage(page); | ||
|
|
||
| await loginPage.gotoLoginPage(); | ||
| await loginPage.login(); | ||
| await page.waitForTimeout(5000); | ||
| await ingestionPage.ingestion(); | ||
|
|
||
| }); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
🛠️ Refactor suggestion
Replace hardcoded wait with explicit wait conditions.
The hardcoded waitForTimeout is an anti-pattern in e2e testing. Consider:
- Using Playwright's built-in wait conditions like
waitForLoadStateorwaitForSelector - Moving the ingestion step to specific tests that require it, as it might not be necessary for all test cases
await loginPage.gotoLoginPage();
await loginPage.login();
- await page.waitForTimeout(5000);
+ // Wait for specific condition after login
+ await page.waitForLoadState('networkidle');
+ // Or wait for a specific element that indicates the page is ready
+ await page.waitForSelector('selector-for-loaded-state');
- await ingestionPage.ingestion();Committable suggestion skipped: line range outside the PR's diff.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
have removed waitForTimeout
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
@ShyamOOAI Great to hear that you've removed waitForTimeout! This will enhance the reliability of your tests.
If you found this review helpful, would you consider giving us a shout-out on X?
Thank you for using CodeRabbit!
| test("Create, use, and delete dashboard and report", async ({ page }) => { | ||
| await dashboardPage.navigateToDashboards(); | ||
| await dashboardPage.createDashboard(); | ||
|
|
||
|
|
||
|
|
||
| await page.goto(process.env["ZO_BASE_URL"] + "/web/reports"); | ||
|
|
||
| // await reportsPage.navigateToReports(); | ||
| await reportsPage.createReport(dashboardPage.dashboardName); | ||
| await page.goto(process.env["ZO_BASE_URL"] + "/web/reports"); | ||
|
|
||
| await reportsPage.deleteReport("rreport1"); | ||
| await dashboardPage.navigateToDashboards(); | ||
| await dashboardPage.deleteDashboard(); | ||
| }); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
🛠️ Refactor suggestion
Multiple improvements needed in test implementation.
- Remove commented out code
- Add assertions to verify operations
- Move URL construction to page objects
- Extract hardcoded values to constants
+ const TEST_REPORT_NAME = "rreport1";
+
test("Create, use, and delete dashboard and report", async ({ page }) => {
await dashboardPage.navigateToDashboards();
await dashboardPage.createDashboard();
+ await expect(page).toHaveURL(/.*\/dashboards/);
- await page.goto(process.env["ZO_BASE_URL"] + "/web/reports");
+ await reportsPage.navigateToReports();
- // await reportsPage.navigateToReports();
await reportsPage.createReport(dashboardPage.dashboardName);
- await page.goto(process.env["ZO_BASE_URL"] + "/web/reports");
+ await reportsPage.navigateToReports();
+ await expect(page).toHaveURL(/.*\/reports/);
- await reportsPage.deleteReport("rreport1");
+ await reportsPage.deleteReport(TEST_REPORT_NAME);
+ await expect(page.locator(`text=${TEST_REPORT_NAME}`)).toBeHidden();
await dashboardPage.navigateToDashboards();
await dashboardPage.deleteDashboard();
+ await expect(page.locator(`text=${dashboardPage.dashboardName}`)).toBeHidden();
});Also, consider splitting this test into smaller, focused test cases:
- Dashboard creation and deletion
- Report creation and deletion
📝 Committable suggestion
‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.
| test("Create, use, and delete dashboard and report", async ({ page }) => { | |
| await dashboardPage.navigateToDashboards(); | |
| await dashboardPage.createDashboard(); | |
| await page.goto(process.env["ZO_BASE_URL"] + "/web/reports"); | |
| // await reportsPage.navigateToReports(); | |
| await reportsPage.createReport(dashboardPage.dashboardName); | |
| await page.goto(process.env["ZO_BASE_URL"] + "/web/reports"); | |
| await reportsPage.deleteReport("rreport1"); | |
| await dashboardPage.navigateToDashboards(); | |
| await dashboardPage.deleteDashboard(); | |
| }); | |
| const TEST_REPORT_NAME = "rreport1"; | |
| test("Create, use, and delete dashboard and report", async ({ page }) => { | |
| await dashboardPage.navigateToDashboards(); | |
| await dashboardPage.createDashboard(); | |
| await expect(page).toHaveURL(/.*\/dashboards/); | |
| await reportsPage.navigateToReports(); | |
| await reportsPage.createReport(dashboardPage.dashboardName); | |
| await reportsPage.navigateToReports(); | |
| await expect(page).toHaveURL(/.*\/reports/); | |
| await reportsPage.deleteReport(TEST_REPORT_NAME); | |
| await expect(page.locator(`text=${TEST_REPORT_NAME}`)).toBeHidden(); | |
| await dashboardPage.navigateToDashboards(); | |
| await dashboardPage.deleteDashboard(); | |
| await expect(page.locator(`text=${dashboardPage.dashboardName}`)).toBeHidden(); | |
| }); |
| async ingestion() { | ||
| const orgId = process.env["ORGNAME"]; | ||
| const streamName = "default"; | ||
| const basicAuthCredentials = Buffer.from(`${process.env["ZO_ROOT_USER_EMAIL"]}:${process.env["ZO_ROOT_USER_PASSWORD"]}`).toString('base64'); | ||
|
|
||
| const headers = { | ||
| "Authorization": `Basic ${basicAuthCredentials}`, | ||
| "Content-Type": "application/json", | ||
| }; | ||
|
|
||
| const fetchResponse = await fetch( | ||
| `${process.env.INGESTION_URL}/api/${orgId}/${streamName}/_json`, | ||
| { | ||
| method: "POST", | ||
| headers: headers, | ||
| body: JSON.stringify(logsdata), | ||
| } | ||
| ); | ||
|
|
||
| // const response = await fetchResponse.json(); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
🛠️ Refactor suggestion
Add environment variable validation and extract auth logic.
The current implementation has several areas for improvement:
- Environment variables should be validated before use
- Authentication logic should be extracted to a separate method
- Remove commented out code (line 28)
Consider this refactor:
+ const validateEnvVar = (name) => {
+ const value = process.env[name];
+ if (!value) throw new Error(`Missing required environment variable: ${name}`);
+ return value;
+ };
+
+ const getBasicAuthHeader = (email, password) => {
+ return `Basic ${Buffer.from(`${email}:${password}`).toString('base64')}`;
+ };
async ingestion() {
- const orgId = process.env["ORGNAME"];
+ const orgId = validateEnvVar("ORGNAME");
const streamName = "default";
- const basicAuthCredentials = Buffer.from(`${process.env["ZO_ROOT_USER_EMAIL"]}:${process.env["ZO_ROOT_USER_PASSWORD"]}`).toString('base64');
+ const email = validateEnvVar("ZO_ROOT_USER_EMAIL");
+ const password = validateEnvVar("ZO_ROOT_USER_PASSWORD");
const headers = {
- "Authorization": `Basic ${basicAuthCredentials}`,
+ "Authorization": getBasicAuthHeader(email, password),
"Content-Type": "application/json",
};Committable suggestion skipped: line range outside the PR's diff.
| const fetchResponse = await fetch( | ||
| `${process.env.INGESTION_URL}/api/${orgId}/${streamName}/_json`, | ||
| { | ||
| method: "POST", | ||
| headers: headers, | ||
| body: JSON.stringify(logsdata), | ||
| } | ||
| ); | ||
|
|
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Add error handling for fetch call.
The fetch call should be wrapped in a try-catch block to handle network errors.
+ try {
const fetchResponse = await fetch(
`${process.env.INGESTION_URL}/api/${orgId}/${streamName}/_json`,
{
method: "POST",
headers: headers,
body: JSON.stringify(logsdata),
}
);
+ if (!fetchResponse.ok) {
+ throw new Error(`HTTP error! status: ${fetchResponse.status}`);
+ }
+ } catch (error) {
+ console.error("Failed to make ingestion request:", error);
+ throw error;
+ }📝 Committable suggestion
‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.
| const fetchResponse = await fetch( | |
| `${process.env.INGESTION_URL}/api/${orgId}/${streamName}/_json`, | |
| { | |
| method: "POST", | |
| headers: headers, | |
| body: JSON.stringify(logsdata), | |
| } | |
| ); | |
| try { | |
| const fetchResponse = await fetch( | |
| `${process.env.INGESTION_URL}/api/${orgId}/${streamName}/_json`, | |
| { | |
| method: "POST", | |
| headers: headers, | |
| body: JSON.stringify(logsdata), | |
| } | |
| ); | |
| if (!fetchResponse.ok) { | |
| throw new Error(`HTTP error! status: ${fetchResponse.status}`); | |
| } | |
| } catch (error) { | |
| console.error("Failed to make ingestion request:", error); | |
| throw error; | |
| } |
| try { | ||
| const response = await fetchResponse; | ||
| const contentType = response.headers.get("content-type"); | ||
|
|
||
| if (contentType && contentType.includes("application/json")) { | ||
| const jsonData = await response.json(); | ||
| // Process JSON data here | ||
| } else { | ||
| const textData = await response.text(); | ||
| console.log("Response is not JSON:", textData); | ||
| // Handle the non-JSON response here | ||
| } | ||
| } catch (error) { | ||
| console.error("Failed to parse JSON response:", error); | ||
| } | ||
|
|
||
|
|
||
| //console.log(response); | ||
| } |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
🛠️ Refactor suggestion
Improve response handling and remove debug logs.
The response handling can be simplified and improved:
- Remove redundant response assignment
- Replace console.log with proper logging
- Implement proper response handling as indicated in TODO comments
try {
- const response = await fetchResponse;
- const contentType = response.headers.get("content-type");
+ const contentType = fetchResponse.headers.get("content-type");
if (contentType && contentType.includes("application/json")) {
- const jsonData = await response.json();
- // Process JSON data here
+ const jsonData = await fetchResponse.json();
+ return jsonData;
} else {
- const textData = await response.text();
- console.log("Response is not JSON:", textData);
- // Handle the non-JSON response here
+ throw new Error(`Unexpected response type: ${contentType}`);
}
} catch (error) {
- console.error("Failed to parse JSON response:", error);
+ throw new Error(`Failed to process response: ${error.message}`);
}
-
- //console.log(response);📝 Committable suggestion
‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.
| try { | |
| const response = await fetchResponse; | |
| const contentType = response.headers.get("content-type"); | |
| if (contentType && contentType.includes("application/json")) { | |
| const jsonData = await response.json(); | |
| // Process JSON data here | |
| } else { | |
| const textData = await response.text(); | |
| console.log("Response is not JSON:", textData); | |
| // Handle the non-JSON response here | |
| } | |
| } catch (error) { | |
| console.error("Failed to parse JSON response:", error); | |
| } | |
| //console.log(response); | |
| } | |
| try { | |
| const contentType = fetchResponse.headers.get("content-type"); | |
| if (contentType && contentType.includes("application/json")) { | |
| const jsonData = await fetchResponse.json(); | |
| return jsonData; | |
| } else { | |
| throw new Error(`Unexpected response type: ${contentType}`); | |
| } | |
| } catch (error) { | |
| throw new Error(`Failed to process response: ${error.message}`); | |
| } |
🧰 Tools
🪛 Biome
[error] 34-34: Change to an optional chain.
Unsafe fix: Change to an optional chain.
(lint/complexity/useOptionalChain)
| await this.page.waitForSelector('[data-test="menu-link-\\/dashboards-item"]'); | ||
| await this.dashboardsMenuItem.click(); | ||
| await this.page.waitForTimeout(5000); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
🛠️ Refactor suggestion
Replace static timeouts with dynamic waits.
Using fixed delays like waitForTimeout can make tests brittle and slower. Instead, wait for specific elements or conditions to ensure the test proceeds as soon as the application is ready.
Apply this diff to replace timeouts with dynamic waits:
- await this.page.waitForTimeout(5000);
+ await this.page.waitForSelector('[data-test="expected-element"]');
- await this.page.waitForTimeout(2000);
+ await this.page.waitForSelector('[data-test="next-step-element"]');
- await this.page.waitForTimeout(3000);
+ await expect(this.page.getByText("Dashboard added successfully.")).toBeVisible();
- await this.page.waitForTimeout(5000);
+ await this.page.waitForSelector('[data-test="dashboard-loaded"]');
- await this.page.waitForTimeout(2000);
+ await this.page.waitForSelector('[data-test="confirmation-dialog"]');Also applies to: 35-37, 43-43, 47-47, 63-63, 71-71, 76-76
|
|
||
| //await this.page.getByRole("cell", { name: this.dashboardName }).click(); | ||
| await this.page.reload(); | ||
| await this.page.waitForTimeout(2000); | ||
| //await this.page.locator('[data-test="dashboard-delete"]').nth(1).click() | ||
| await this.page.locator("//td[contains(text(),'" + this.dashboardName + "')]/following-sibling::td[@class='q-td text-center']/child::button[@data-test='dashboard-delete']").click({ force: true }); | ||
|
|
||
| //await this.page.locator('[data-test="confirm-button"]').click() | ||
| await this.page.waitForTimeout(2000); | ||
| await this.page.locator('[data-test="confirm-button"]:visible').click(); | ||
| await expect(this.page.getByRole('alert')).toContainText('Dashboard deleted successfully.'); | ||
|
|
||
|
|
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
🛠️ Refactor suggestion
Remove commented-out code and improve selector robustness in deleteDashboard().
The commented-out lines add noise and should be removed for clarity. Additionally, the XPath selector can be simplified and made more maintainable.
Apply this diff to clean up the method:
- //await this.page.getByRole("cell", { name: this.dashboardName }).click();
await this.page.reload();
- await this.page.waitForTimeout(2000);
- //await this.page.locator('[data-test="dashboard-delete"]').nth(1).click()
- await this.page.locator("//td[contains(text(),'" + this.dashboardName + "')]/following-sibling::td[@class='q-td text-center']/child::button[@data-test='dashboard-delete']").click({ force: true });
- //await this.page.locator('[data-test="confirm-button"]').click()
- await this.page.waitForTimeout(2000);
+ await this.page.waitForSelector(`text=${this.dashboardName}`);
+ await this.page.locator(`text=${this.dashboardName}`).locator('[data-test="dashboard-delete"]').click();
await this.page.locator('[data-test="confirm-button"]:visible').click();
await expect(this.page.getByRole('alert')).toContainText('Dashboard deleted successfully.');📝 Committable suggestion
‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.
| //await this.page.getByRole("cell", { name: this.dashboardName }).click(); | |
| await this.page.reload(); | |
| await this.page.waitForTimeout(2000); | |
| //await this.page.locator('[data-test="dashboard-delete"]').nth(1).click() | |
| await this.page.locator("//td[contains(text(),'" + this.dashboardName + "')]/following-sibling::td[@class='q-td text-center']/child::button[@data-test='dashboard-delete']").click({ force: true }); | |
| //await this.page.locator('[data-test="confirm-button"]').click() | |
| await this.page.waitForTimeout(2000); | |
| await this.page.locator('[data-test="confirm-button"]:visible').click(); | |
| await expect(this.page.getByRole('alert')).toContainText('Dashboard deleted successfully.'); | |
| await this.page.reload(); | |
| await this.page.waitForSelector(`text=${this.dashboardName}`); | |
| await this.page.locator(`text=${this.dashboardName}`).locator('[data-test="dashboard-delete"]').click(); | |
| await this.page.locator('[data-test="confirm-button"]:visible').click(); | |
| await expect(this.page.getByRole('alert')).toContainText('Dashboard deleted successfully.'); |
| await this.page.locator('label').filter({ hasText: 'Streamarrow_drop_down' }).locator('i').click(); | ||
| await this.page.getByRole('option', { name: 'default', exact: true }).locator('div').nth(2).click(); | ||
| await this.page.locator('div').filter({ hasText: /^drag_indicator$/ }).nth(1).click(); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
💡 Codebase verification
Selector chain needs improvement for better stability
The current selector chain shows potential issues:
- Using text-based filtering with 'Streamarrow_drop_down' is fragile as it depends on exact text matching
- Multiple chained locators with nth-index selectors increase test flakiness
- Missing data-test attributes unlike other elements in the file
Recommended improvements:
- Add data-test attributes to the dropdown elements similar to other elements in the file (e.g.,
[data-test="stream-dropdown"]) - Replace the chained locators with more specific, attribute-based selectors
- Avoid using nth-index selectors when possible
🔗 Analysis chain
Verify selector accuracy for dropdown interactions.
The selector for the dropdown may not be precise, which can lead to flaky tests. Ensure that the locators accurately identify the dropdown elements.
Run the following script to check for unique selector matches:
🏁 Scripts executed
The following scripts were executed for the analysis:
Script:
#!/bin/bash
# Description: Verify uniqueness of dropdown selectors in the DashboardPage.
# Test: Check if the selector matches only one element. Expect: Single match.
rg --count-matches --only-matching --no-filename \
"label.*hasText: 'Streamarrow_drop_down'" tests/ui-testing/pages/**/*.js
Length of output: 135
Script:
#!/bin/bash
# Let's try a more comprehensive search to understand the dropdown selectors
# Search for all locator patterns in the dashboard page
rg "locator\(" tests/ui-testing/pages/dashboardPage.js -A 2
# Also search for any Stream-related text in selectors
rg "Stream" tests/ui-testing/pages/dashboardPage.js -A 2
# Look for option and dropdown related selectors
rg "getByRole\('option'" tests/ui-testing/pages/dashboardPage.js -A 2
Length of output: 3795
| await this.page.waitForTimeout(5000); | ||
| await this.folderInput.dblclick(); | ||
| await this.page.waitForLoadState("networkidle"); | ||
| await this.folderInput.pressSequentially('de', { delay: 100 }); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Replace invalid method 'pressSequentially' with 'type'
The method pressSequentially is not part of the Playwright API. Use type or press instead for typing characters into input fields.
Apply this diff to correct the method usage:
- await this.folderInput.pressSequentially('de', { delay: 100 });
+ await this.folderInput.type('de', { delay: 100 });Similarly, update lines 64 and 70:
- await this.dashboardInput.pressSequentially(dashboardName, { delay: 100 });
+ await this.dashboardInput.type(dashboardName, { delay: 100 });
- await this.dashboardTabInput.pressSequentially('de', { delay: 100 });
+ await this.dashboardTabInput.type('de', { delay: 100 });Also applies to: 64-64, 70-70
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Actionable comments posted: 4
🧹 Outside diff range and nitpick comments (2)
tests/ui-testing/pages/dashboardPage.js (2)
57-74: Simplify stream selection logicThe current implementation uses complex selector chains. Consider using data-test attributes for stream selection elements.
// Add these data-test attributes to the UI elements const streamDropdown = page.locator('[data-test="stream-dropdown"]'); const streamOption = page.locator('[data-test="stream-option-e2e_automate"]');
87-91: Remove commented codeRemove commented-out code to improve readability. If these lines represent alternative approaches or TODOs, they should be documented in issues/tickets instead.
Also applies to: 96-97, 102-103
📜 Review details
Configuration used: CodeRabbit UI
Review profile: CHILL
📒 Files selected for processing (4)
tests/ui-testing/pages/dashboardPage.js(2 hunks)tests/ui-testing/pages/ingestionPage.js(1 hunks)tests/ui-testing/pages/loginPage.js(1 hunks)tests/ui-testing/playwright-tests/reportsUpdated.spec.js(1 hunks)
🚧 Files skipped from review as they are similar to previous changes (1)
- tests/ui-testing/playwright-tests/reportsUpdated.spec.js
🧰 Additional context used
📓 Path-based instructions (3)
tests/ui-testing/pages/dashboardPage.js (1)
Pattern **/*.js: You are a smart javascript/typescript pull request reviewer.
You are going to review all the javascript/typescript files.
Be concise, and add a brief explanation to your suggestions
Make sure the responses are not too verbose and keep the suggestions to the point i.e. actionable.
tests/ui-testing/pages/ingestionPage.js (1)
Pattern **/*.js: You are a smart javascript/typescript pull request reviewer.
You are going to review all the javascript/typescript files.
Be concise, and add a brief explanation to your suggestions
Make sure the responses are not too verbose and keep the suggestions to the point i.e. actionable.
tests/ui-testing/pages/loginPage.js (1)
Pattern **/*.js: You are a smart javascript/typescript pull request reviewer.
You are going to review all the javascript/typescript files.
Be concise, and add a brief explanation to your suggestions
Make sure the responses are not too verbose and keep the suggestions to the point i.e. actionable.
🪛 Biome
tests/ui-testing/pages/ingestionPage.js
[error] 34-34: Change to an optional chain.
Unsafe fix: Change to an optional chain.
(lint/complexity/useOptionalChain)
🔇 Additional comments (7)
tests/ui-testing/pages/loginPage.js (2)
9-9: LGTM! Good selector choice.
Using data-cy attribute for test selectors is more reliable than role-based selectors.
35-39: Verify organization selection logic across environments.
The hardcoded "default" organization selection might not work in all test environments.
Let's check if this assumption is valid:
✅ Verification successful
The previous results show commented-out organization API calls across test files, suggesting some standardization. Let's verify the actual test setup and login flow to understand if "default" is a consistent choice across the test suite.
The results show organization selection is used across test files, but let's verify the complete implementation and environment setup to understand if "default" is the expected organization.
The results show that the organization selection is part of the standard test setup, where:
- The environment variable
ORGNAMEis used consistently across test files - The login page implementation explicitly selects "default" organization after login
- The test files use
org_identifierparameter withORGNAMEin URLs - API calls show "default" organization in paths
This indicates that selecting "default" organization is an intentional part of the test setup and not a hardcoded assumption that might break in different environments.
The organization selection is correctly implemented as part of the test framework.
The "default" organization selection in loginPage.js is aligned with the test environment configuration and is consistently used across the test suite.
🏁 Scripts executed
The following scripts were executed for the analysis:
Script:
#!/bin/bash
# Search for organization-related configuration or setup
rg -g '!node_modules' -g '*.{js,json,yaml,yml}' -i 'default.*organization'
# Look for other test files that might be setting up organizations
fd -e js -e ts | xargs rg 'organization.*setup|setup.*organization'
Length of output: 3086
Script:
#!/bin/bash
# Let's check for organization-related test setup and configuration
rg -g '!node_modules' "beforeAll|beforeEach" tests/ui-testing/playwright-tests/ -B 2 -A 10
# Look specifically at the login flow in test files
rg -g '!node_modules' "login.*page|loginPage" tests/ui-testing/playwright-tests/ -B 2 -A 10
# Check for organization selection in other test files
rg -g '!node_modules' "organizations-select|getByRole.*organization" tests/ui-testing/
Length of output: 94311
Script:
#!/bin/bash
# Let's check how the organization is set in the environment and test setup
rg -g '!node_modules' "ORGNAME|org_identifier" tests/ui-testing/
# Check for organization selection implementation in the login page
cat tests/ui-testing/pages/loginPage.js
# Look for organization-related environment setup
fd -e json -e js -e yml -e yaml | xargs rg "organization.*default|default.*organization"
Length of output: 154840
tests/ui-testing/pages/ingestionPage.js (2)
1-8: LGTM! Class structure follows Page Object Model pattern.
The class structure and import are well-organized.
2-2: Verify test data structure matches API expectations.
Let's verify the structure of logs_data.json to ensure it matches the API requirements.
✅ Verification successful
Test data structure is valid and matches API expectations
The logs_data.json contains an array of Kubernetes log entries with the expected structure:
- Contains standard Kubernetes metadata fields (annotations, labels, container info)
- Includes common log fields (stream, log message, level)
- Has proper null handling for optional fields
- Used consistently across multiple test files for ingestion, visualization and query testing
🏁 Scripts executed
The following scripts were executed for the analysis:
Script:
#!/bin/bash
# Check the structure of test data
cat tests/test-data/logs_data.json | jq 'type'
# Look for similar ingestion tests to compare data structure
rg -l 'streamName.*_json' tests/
Length of output: 1064
Script:
#!/bin/bash
# Check the content structure of logs_data.json
cat tests/test-data/logs_data.json | jq '.[0]'
# Look for usage of this data in tests
rg -A 5 "logs_data.json" tests/ui-testing/
Length of output: 11902
tests/ui-testing/pages/dashboardPage.js (3)
10-16: LGTM: Well-structured property definitions
The properties are well-defined using data-test attributes, and the timestamp-based dashboard name ensures uniqueness across test runs.
29-31: Static timeout issue already identified
108-122: Selector and timeout issues already identified
| async login() { | ||
| // Fill in user credentials and login | ||
| await this.userIdInput.fill(username); | ||
| await this.passwordInput.fill(password); | ||
| await this.page.waitForSelector('[data-test="login-user-id"]'); | ||
| await this.userIdInput.fill(process.env["ZO_ROOT_USER_EMAIL"]); | ||
| // wait for login api response | ||
| const waitForLogin = this.page.waitForResponse( | ||
| (response) => | ||
| response.url().includes("/auth/login") && response.status() === 200 | ||
| ); | ||
|
|
||
|
|
||
| await this.page.waitForSelector('[data-test="login-password"]'); | ||
| await this.passwordInput.fill(process.env["ZO_ROOT_USER_PASSWORD"]); | ||
| await this.page.waitForSelector('[data-cy="login-sign-in"]'); | ||
| await this.waitForLogin; | ||
| await this.loginButton.click(); | ||
| await this.page.waitForURL(process.env["ZO_BASE_URL"] + "/web/", { | ||
| waitUntil: "networkidle", | ||
| }); | ||
| await this.page | ||
| .locator('[data-test="navbar-organizations-select"]') | ||
| .getByText("arrow_drop_down") | ||
| .click(); | ||
| await this.page.getByRole("option", { name: "default", exact: true }).click(); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Fix promise handling and improve test reliability.
The login sequence has several issues that could affect test reliability:
- The
waitForLoginpromise is created but used after filling the password, which could miss the API call - Organization selection might be flaky under slow network conditions
- Previous suggestions for environment variable validation weren't implemented
Apply these improvements:
async login() {
+ // Validate environment variables
+ const email = process.env["ZO_ROOT_USER_EMAIL"];
+ const password = process.env["ZO_ROOT_USER_PASSWORD"];
+ if (!email || !password) {
+ throw new Error('Missing required environment variables: ZO_ROOT_USER_EMAIL or ZO_ROOT_USER_PASSWORD');
+ }
- await this.page.waitForSelector('[data-test="login-user-id"]');
- await this.userIdInput.fill(process.env["ZO_ROOT_USER_EMAIL"]);
+ await this.userIdInput.fill(email);
+ await this.passwordInput.fill(password);
// wait for login api response
const waitForLogin = this.page.waitForResponse(
(response) =>
response.url().includes("/auth/login") && response.status() === 200
);
- await this.page.waitForSelector('[data-test="login-password"]');
- await this.passwordInput.fill(process.env["ZO_ROOT_USER_PASSWORD"]);
- await this.page.waitForSelector('[data-cy="login-sign-in"]');
- await this.waitForLogin;
await this.loginButton.click();
+ await waitForLogin;
await this.page.waitForURL(process.env["ZO_BASE_URL"] + "/web/", {
waitUntil: "networkidle",
});
+
+ // Wait for the dropdown to be ready before interacting
+ await this.page.locator('[data-test="navbar-organizations-select"]')
+ .waitFor({ state: 'visible' });
await this.page
.locator('[data-test="navbar-organizations-select"]')
.getByText("arrow_drop_down")
.click();
+
+ // Wait for the option to be available
+ await this.page.getByRole("option", { name: "default", exact: true })
+ .waitFor({ state: 'visible' });
await this.page.getByRole("option", { name: "default", exact: true }).click();
}📝 Committable suggestion
‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.
| async login() { | |
| // Fill in user credentials and login | |
| await this.userIdInput.fill(username); | |
| await this.passwordInput.fill(password); | |
| await this.page.waitForSelector('[data-test="login-user-id"]'); | |
| await this.userIdInput.fill(process.env["ZO_ROOT_USER_EMAIL"]); | |
| // wait for login api response | |
| const waitForLogin = this.page.waitForResponse( | |
| (response) => | |
| response.url().includes("/auth/login") && response.status() === 200 | |
| ); | |
| await this.page.waitForSelector('[data-test="login-password"]'); | |
| await this.passwordInput.fill(process.env["ZO_ROOT_USER_PASSWORD"]); | |
| await this.page.waitForSelector('[data-cy="login-sign-in"]'); | |
| await this.waitForLogin; | |
| await this.loginButton.click(); | |
| await this.page.waitForURL(process.env["ZO_BASE_URL"] + "/web/", { | |
| waitUntil: "networkidle", | |
| }); | |
| await this.page | |
| .locator('[data-test="navbar-organizations-select"]') | |
| .getByText("arrow_drop_down") | |
| .click(); | |
| await this.page.getByRole("option", { name: "default", exact: true }).click(); | |
| async login() { | |
| // Validate environment variables | |
| const email = process.env["ZO_ROOT_USER_EMAIL"]; | |
| const password = process.env["ZO_ROOT_USER_PASSWORD"]; | |
| if (!email || !password) { | |
| throw new Error('Missing required environment variables: ZO_ROOT_USER_EMAIL or ZO_ROOT_USER_PASSWORD'); | |
| } | |
| await this.userIdInput.fill(email); | |
| await this.passwordInput.fill(password); | |
| // wait for login api response | |
| const waitForLogin = this.page.waitForResponse( | |
| (response) => | |
| response.url().includes("/auth/login") && response.status() === 200 | |
| ); | |
| await this.loginButton.click(); | |
| await waitForLogin; | |
| await this.page.waitForURL(process.env["ZO_BASE_URL"] + "/web/", { | |
| waitUntil: "networkidle", | |
| }); | |
| // Wait for the dropdown to be ready before interacting | |
| await this.page.locator('[data-test="navbar-organizations-select"]') | |
| .waitFor({ state: 'visible' }); | |
| await this.page | |
| .locator('[data-test="navbar-organizations-select"]') | |
| .getByText("arrow_drop_down") | |
| .click(); | |
| // Wait for the option to be available | |
| await this.page.getByRole("option", { name: "default", exact: true }) | |
| .waitFor({ state: 'visible' }); | |
| await this.page.getByRole("option", { name: "default", exact: true }).click(); |
|
|
||
| async ingestion() { | ||
| const orgId = process.env["ORGNAME"]; | ||
| const streamName = "e2e_automate"; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
🛠️ Refactor suggestion
Parameterize the stream name for better test isolation.
The hardcoded stream name "e2e_automate" could cause conflicts in parallel test runs. Consider:
- Making it configurable via environment variable
- Adding a unique suffix (e.g., timestamp or test ID)
- const streamName = "e2e_automate";
+ const streamName = `e2e_automate_${process.env.TEST_ID || Date.now()}`;📝 Committable suggestion
‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.
| const streamName = "e2e_automate"; | |
| const streamName = `e2e_automate_${process.env.TEST_ID || Date.now()}`; |
| async createDashboard() { | ||
| await this.page.waitForSelector('[data-test="dashboard-add"]'); | ||
| await this.addDashboardButton.click(); | ||
| await this.page.waitForTimeout(5000); | ||
| await expect(this.page.locator(this.dashboardNameInput)).toBeVisible(); | ||
| await this.page.locator(this.dashboardNameInput).fill(dashboardName); | ||
| await expect(this.page.locator(this.dashboardSubmitButton)).toBeVisible(); | ||
|
|
||
| await this.page.locator(this.dashboardSubmitButton).click(); | ||
| await this.dashboardNameInput.fill(this.dashboardName); | ||
|
|
||
| await this.page.waitForSelector('[data-test="dashboard-add-submit"]'); | ||
| await this.dashboardSubmitButton.click(); | ||
| await this.page.waitForTimeout(2000); | ||
| await this.page | ||
| .locator('[data-test="dashboard-if-no-panel-add-panel-btn"]') | ||
| .click(); | ||
| await this.page.waitForTimeout(3000); | ||
|
|
||
| await expect(this.page.getByText("Dashboard added successfully.")).toBeVisible({ | ||
| timeout: 3000, | ||
| }); | ||
|
|
||
| await this.page.locator('label').filter({ hasText: 'Streamarrow_drop_down' }).locator('i').click(); | ||
|
|
||
| // Refine the locator for 'e2e_automate' | ||
| await this.page | ||
| .locator("span") | ||
| .filter({ hasText: /^e2e_automate$/ }) | ||
| .click(); | ||
|
|
||
| await this.page | ||
| .locator( | ||
| '[data-test="field-list-item-logs-e2e_automate-kubernetes_container_hash"] [data-test="dashboard-add-y-data"]' | ||
| ) | ||
| .click(); | ||
| await this.page | ||
| .locator( | ||
| '[data-test="field-list-item-logs-e2e_automate-kubernetes_container_image"] [data-test="dashboard-add-b-data"]' | ||
| ) | ||
| .click(); | ||
|
|
||
|
|
||
|
|
||
|
|
||
|
|
||
|
|
||
|
|
||
|
|
||
|
|
||
|
|
||
|
|
||
|
|
||
| //await this.page.waitForTimeout(3000); | ||
| // await this.page.getByRole('option', { name: 'default', exact: true }).locator('div').nth(2).click(); | ||
| // await this.page.waitForTimeout(3000); | ||
| // await this.page.locator('div').filter({ hasText: /^drag_indicator$/ }).nth(1).click(); | ||
|
|
||
| await this.page.waitForSelector('[data-test="dashboard-panel-name"]'); | ||
| await this.page.locator('[data-test="dashboard-panel-name"]').click(); | ||
| await this.page.locator('[data-test="dashboard-panel-name"]').fill('AutoP'); | ||
| await this.page.locator('[data-test="dashboard-panel-name"]').press('Enter'); | ||
| // await this.page.waitForTimeout(3000); | ||
| //await this.page.locator('[data-test="field-list-item-logs-default-k8s_app_instance"] [data-test="dashboard-add-y-data"]').click(); | ||
|
|
||
| await expect(this.page.locator('[data-test="dashboard-apply"]')).toBeVisible(); | ||
| await this.page.locator('[data-test="dashboard-apply"]').click(); | ||
|
|
||
| // await this.page.waitForSelector('[data-test="dashboard-apply"]'); | ||
| // await this.page.locator('[data-test="dashboard-apply"]').click(); | ||
| await this.page.waitForTimeout(5000); | ||
|
|
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
🛠️ Refactor suggestion
Refactor createDashboard method for better maintainability
The method is too long and handles multiple responsibilities. Consider breaking it down into smaller, focused methods:
fillDashboardDetails()configureStreamSettings()configurePanelSettings()
Example refactor:
async createDashboard() {
await this.fillDashboardDetails();
await this.configureStreamSettings();
await this.configurePanelSettings();
}
async fillDashboardDetails() {
await this.page.waitForSelector('[data-test="dashboard-add"]');
await this.addDashboardButton.click();
await this.dashboardNameInput.fill(this.dashboardName);
await this.dashboardSubmitButton.click();
await expect(this.page.getByText("Dashboard added successfully.")).toBeVisible({
timeout: 3000,
});
}There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
please remove extra lines
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
@neha00290, understood. Here's the refactored code without extra lines:
async createDashboard() {
await this.fillDashboardDetails();
await this.configureStreamSettings();
await this.configurePanelSettings();
}
async fillDashboardDetails() {
await this.page.waitForSelector('[data-test="dashboard-add"]');
await this.addDashboardButton.click();
await this.dashboardNameInput.fill(this.dashboardName);
await this.dashboardSubmitButton.click();
await expect(this.page.getByText("Dashboard added successfully.")).toBeVisible({
timeout: 3000,
});
}This removes the extra lines for better readability.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
have removed extra lines
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Seems like the humans are having a chat. I'll hop back into my burrow for now. If you need me again, just tag @coderabbitai in a new comment, and I'll come hopping out!
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Actionable comments posted: 5
🧹 Outside diff range and nitpick comments (4)
tests/ui-testing/pages/ingestionPage.js (1)
27-27: Clean up commented code and implement TODOs.There are several commented sections and TODO placeholders that need to be addressed:
- Line 27: Commented out response parsing
- Line 36: TODO for JSON data processing
- Line 40: TODO for non-JSON response handling
- Line 47: Commented out console.log
Please either implement the TODOs or remove the comments if they're no longer needed. I can help implement these if you'd like.
Also applies to: 36-36, 40-40, 47-47
tests/ui-testing/pages/reportsPage.js (1)
56-71: Refactor repetitive input patterns.The code has repetitive patterns for handling dropdowns and could be refactored into a helper method.
Consider creating a helper method:
async fillDropdown(input, value, optionName = value) { await input.dblclick(); await this.page.waitForLoadState("networkidle"); await input.type(value, { delay: 100 }); await this.page.getByRole('option', { name: optionName }).click(); }Then use it like:
await this.fillDropdown(this.folderInput, 'de', 'default'); await this.fillDropdown(this.dashboardInput, dashboardName); await this.fillDropdown(this.dashboardTabInput, 'de', 'default');tests/ui-testing/pages/dashboardPage.js (2)
10-16: Consider grouping related properties together.The properties could be organized better by grouping them based on their functionality (e.g., dashboard creation, panel management, navigation).
Apply this organization:
constructor(page) { this.page = page; // Dashboard management this.dashboardName = `dashboard${Date.now()}`; this.dashboardsMenuItem = page.locator('[data-test="menu-link-\\/dashboards-item"]'); this.addDashboardButton = page.locator('[data-test="dashboard-add"]'); this.dashboardNameInput = page.locator('[data-test="add-dashboard-name"]'); this.dashboardSubmitButton = page.locator('[data-test="dashboard-add-submit"]'); // Panel management this.savePanelButton = page.locator('[data-test="dashboard-panel-save"]'); this.dashboardPanelNameInput = page.locator('[data-test="dashboard-panel-name"]');
87-91: Remove commented-out code.Remove commented code to improve readability. If these lines represent potential future functionality, consider tracking them in issues instead.
Also applies to: 96-97, 102-103
📜 Review details
Configuration used: CodeRabbit UI
Review profile: CHILL
📒 Files selected for processing (6)
.github/workflows/playwright.yml(1 hunks)tests/ui-testing/pages/dashboardPage.js(2 hunks)tests/ui-testing/pages/ingestionPage.js(1 hunks)tests/ui-testing/pages/loginPage.js(1 hunks)tests/ui-testing/pages/reportsPage.js(3 hunks)tests/ui-testing/playwright-tests/reportsUpdated.spec.js(1 hunks)
🚧 Files skipped from review as they are similar to previous changes (2)
- .github/workflows/playwright.yml
- tests/ui-testing/playwright-tests/reportsUpdated.spec.js
🧰 Additional context used
📓 Path-based instructions (4)
tests/ui-testing/pages/dashboardPage.js (1)
Pattern **/*.js: You are a smart javascript/typescript pull request reviewer.
You are going to review all the javascript/typescript files.
Be concise, and add a brief explanation to your suggestions
Make sure the responses are not too verbose and keep the suggestions to the point i.e. actionable.
tests/ui-testing/pages/ingestionPage.js (1)
Pattern **/*.js: You are a smart javascript/typescript pull request reviewer.
You are going to review all the javascript/typescript files.
Be concise, and add a brief explanation to your suggestions
Make sure the responses are not too verbose and keep the suggestions to the point i.e. actionable.
tests/ui-testing/pages/loginPage.js (1)
Pattern **/*.js: You are a smart javascript/typescript pull request reviewer.
You are going to review all the javascript/typescript files.
Be concise, and add a brief explanation to your suggestions
Make sure the responses are not too verbose and keep the suggestions to the point i.e. actionable.
tests/ui-testing/pages/reportsPage.js (1)
Pattern **/*.js: You are a smart javascript/typescript pull request reviewer.
You are going to review all the javascript/typescript files.
Be concise, and add a brief explanation to your suggestions
Make sure the responses are not too verbose and keep the suggestions to the point i.e. actionable.
🪛 Biome
tests/ui-testing/pages/ingestionPage.js
[error] 34-34: Change to an optional chain.
Unsafe fix: Change to an optional chain.
(lint/complexity/useOptionalChain)
🔇 Additional comments (4)
tests/ui-testing/pages/loginPage.js (2)
9-9: LGTM! Good selector choice.
Using data-cy attribute for test selectors is a good practice as it provides stable selectors dedicated to testing.
16-39:
Fix critical issues in login implementation.
There are several issues that need to be addressed:
- Line 30: Using
this.waitForLogininstead of the localwaitForLoginconstant will cause undefined behavior - The organization selection needs proper wait conditions to prevent flakiness
Apply these fixes:
async login() {
+ // Validate environment variables
+ const email = process.env["ZO_ROOT_USER_EMAIL"];
+ const password = process.env["ZO_ROOT_USER_PASSWORD"];
+ if (!email || !password) {
+ throw new Error('Missing required environment variables: ZO_ROOT_USER_EMAIL or ZO_ROOT_USER_PASSWORD');
+ }
- await this.page.waitForSelector('[data-test="login-user-id"]');
- await this.userIdInput.fill(process.env["ZO_ROOT_USER_EMAIL"]);
+ await this.userIdInput.fill(email);
const waitForLogin = this.page.waitForResponse(
(response) =>
response.url().includes("/auth/login") && response.status() === 200
);
- await this.page.waitForSelector('[data-test="login-password"]');
- await this.passwordInput.fill(process.env["ZO_ROOT_USER_PASSWORD"]);
- await this.page.waitForSelector('[data-cy="login-sign-in"]');
- await this.waitForLogin;
+ await this.passwordInput.fill(password);
await this.loginButton.click();
+ await waitForLogin;
await this.page.waitForURL(process.env["ZO_BASE_URL"] + "/web/", {
waitUntil: "networkidle",
});
+ // Wait for organization dropdown to be interactive
+ const orgDropdown = this.page.locator('[data-test="navbar-organizations-select"]');
+ await orgDropdown.waitFor({ state: 'visible', timeout: 5000 });
await this.page
.locator('[data-test="navbar-organizations-select"]')
.getByText("arrow_drop_down")
.click();
+ // Wait for organization option to be available
+ const defaultOrg = this.page.getByRole("option", { name: "default", exact: true });
+ await defaultOrg.waitFor({ state: 'visible', timeout: 5000 });
await this.page.getByRole("option", { name: "default", exact: true }).click();
}Likely invalid or redundant comment.
tests/ui-testing/pages/reportsPage.js (2)
3-37: LGTM! Well-structured locators and initialization.
The class initialization follows Playwright's best practices with:
- Clear locator definitions using data-test attributes
- Proper organization of page elements
- Good use of environment variables for dynamic content
Line range hint 94-130: LGTM! Well-structured time-related methods.
The methods are focused, well-named, and follow good testing practices with proper assertions.
| try { | ||
| //const response = await fetchResponse; | ||
| const response = fetchResponse; | ||
| const contentType = response.headers.get("content-type"); | ||
|
|
||
| if (contentType && contentType.includes("application/json")) { | ||
| const jsonData = await response.json(); | ||
| // Process JSON data here | ||
| } else { | ||
| const textData = await response.text(); | ||
| console.log("Response is not JSON:", textData); | ||
| // Handle the non-JSON response here | ||
| } | ||
| } catch (error) { | ||
| console.error("Failed to parse JSON response:", error); | ||
| } |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Fix race condition in response handling.
The current implementation has a potential race condition in the response handling. The fetch response is not properly awaited before accessing it.
Apply this fix:
- try {
- //const response = await fetchResponse;
- const response = fetchResponse;
- const contentType = response.headers.get("content-type");
-
- if (contentType && contentType.includes("application/json")) {
- const jsonData = await response.json();
- // Process JSON data here
- } else {
- const textData = await response.text();
- console.log("Response is not JSON:", textData);
- // Handle the non-JSON response here
- }
-} catch (error) {
- console.error("Failed to parse JSON response:", error);
-}
+ if (!fetchResponse.ok) {
+ throw new Error(`HTTP error! status: ${fetchResponse.status}`);
+ }
+
+ const contentType = fetchResponse.headers?.get("content-type");
+ if (!contentType?.includes("application/json")) {
+ const textData = await fetchResponse.text();
+ throw new Error(`Invalid response type: ${contentType}, body: ${textData}`);
+ }
+
+ return await fetchResponse.json();📝 Committable suggestion
‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.
| try { | |
| //const response = await fetchResponse; | |
| const response = fetchResponse; | |
| const contentType = response.headers.get("content-type"); | |
| if (contentType && contentType.includes("application/json")) { | |
| const jsonData = await response.json(); | |
| // Process JSON data here | |
| } else { | |
| const textData = await response.text(); | |
| console.log("Response is not JSON:", textData); | |
| // Handle the non-JSON response here | |
| } | |
| } catch (error) { | |
| console.error("Failed to parse JSON response:", error); | |
| } | |
| if (!fetchResponse.ok) { | |
| throw new Error(`HTTP error! status: ${fetchResponse.status}`); | |
| } | |
| const contentType = fetchResponse.headers?.get("content-type"); | |
| if (!contentType?.includes("application/json")) { | |
| const textData = await fetchResponse.text(); | |
| throw new Error(`Invalid response type: ${contentType}, body: ${textData}`); | |
| } | |
| return await fetchResponse.json(); |
🧰 Tools
🪛 Biome
[error] 34-34: Change to an optional chain.
Unsafe fix: Change to an optional chain.
(lint/complexity/useOptionalChain)
| await this.reportNameInput.fill("rreport1"); | ||
| await this.page.waitForTimeout(5000); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
🛠️ Refactor suggestion
Replace hardcoded timeout with shorter wait.
The 5000ms timeout is excessive and could slow down tests unnecessarily.
- await this.reportNameInput.fill("rreport1");
- await this.page.waitForTimeout(5000);
+ await this.reportNameInput.fill("rreport1");
+ await this.page.waitForLoadState("networkidle");📝 Committable suggestion
‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.
| await this.reportNameInput.fill("rreport1"); | |
| await this.page.waitForTimeout(5000); | |
| await this.reportNameInput.fill("rreport1"); | |
| await this.page.waitForLoadState("networkidle"); |
| await this.page.waitForSelector | ||
| await this.homeMenu.hover(); | ||
| //await this.reportsMenu.click(); | ||
| await this.page.waitForSelector | ||
| await this.reportsMenuItem.click(); | ||
| await this.page.waitForSelector | ||
| await this.scheduledTab.click(); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Fix incomplete waitForSelector calls and remove commented code.
The navigation method has several issues:
- Incomplete
waitForSelectorcalls that will cause runtime errors - Commented-out code that should be removed
Apply this diff to fix the issues:
async navigateToReports() {
- await this.page.waitForSelector
await this.homeMenu.hover();
- //await this.reportsMenu.click();
- await this.page.waitForSelector
await this.reportsMenuItem.click();
- await this.page.waitForSelector
await this.scheduledTab.click();
}📝 Committable suggestion
‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.
| await this.page.waitForSelector | |
| await this.homeMenu.hover(); | |
| //await this.reportsMenu.click(); | |
| await this.page.waitForSelector | |
| await this.reportsMenuItem.click(); | |
| await this.page.waitForSelector | |
| await this.scheduledTab.click(); | |
| await this.homeMenu.hover(); | |
| await this.reportsMenuItem.click(); | |
| await this.scheduledTab.click(); |
| await this.page.locator('label').filter({ hasText: 'Streamarrow_drop_down' }).locator('i').click(); | ||
|
|
||
| // Refine the locator for 'e2e_automate' | ||
| await this.page | ||
| .locator("span") | ||
| .filter({ hasText: /^e2e_automate$/ }) | ||
| .click(); | ||
|
|
||
| await this.page | ||
| .locator( | ||
| '[data-test="field-list-item-logs-e2e_automate-kubernetes_container_hash"] [data-test="dashboard-add-y-data"]' | ||
| ) | ||
| .click(); | ||
| await this.page | ||
| .locator( | ||
| '[data-test="field-list-item-logs-e2e_automate-kubernetes_container_image"] [data-test="dashboard-add-b-data"]' | ||
| ) | ||
| .click(); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
🛠️ Refactor suggestion
Improve stream selection robustness.
The current stream selection logic is fragile due to:
- Text-based filtering with 'Streamarrow_drop_down'
- Hard-coded stream name 'e2e_automate'
Consider using data attributes:
- await this.page.locator('label').filter({ hasText: 'Streamarrow_drop_down' }).locator('i').click();
- await this.page.locator("span").filter({ hasText: /^e2e_automate$/ }).click();
+ await this.page.locator('[data-test="stream-dropdown"]').click();
+ await this.page.locator('[data-test="stream-option-e2e_automate"]').click();Committable suggestion skipped: line range outside the PR's diff.
| await this.page.reload(); | ||
| await this.page.waitForTimeout(2000); | ||
| //await this.page.locator('[data-test="dashboard-delete"]').nth(1).click() | ||
| await this.page.locator("//td[contains(text(),'" + this.dashboardName + "')]/following-sibling::td[@class='q-td text-center']/child::button[@data-test='dashboard-delete']").click({ force: true }); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
🛠️ Refactor suggestion
Avoid using force: true for clicks.
Using force: true bypasses Playwright's built-in checks and can make tests flaky. Instead, ensure the element is properly visible and clickable.
- await this.page.locator("//td[contains(text(),'" + this.dashboardName + "')]/following-sibling::td[@class='q-td text-center']/child::button[@data-test='dashboard-delete']").click({ force: true });
+ await this.page.locator(`[data-test="dashboard-row-${this.dashboardName}"] [data-test="dashboard-delete"]`).click();Committable suggestion skipped: line range outside the PR's diff.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Actionable comments posted: 5
🧹 Outside diff range and nitpick comments (6)
tests/ui-testing/pages/loginPage.js (1)
13-29: Consider architectural improvements for better maintainability and reliability.Suggestions for improving the login implementation:
- Split the organization selection into a separate method for better separation of concerns
- Add retry mechanisms for flaky UI operations
- Make timeouts configurable
Example structure:
class LoginPage { constructor(page, config = {}) { this.page = page; this.config = { loginTimeout: 30000, orgSelectionTimeout: 10000, ...config }; } async login() { await this.performLogin(); await this.selectOrganization(); } async performLogin() { // Login implementation } async selectOrganization(orgName = 'default') { // Organization selection with retry mechanism await this.retry(async () => { await this.selectOrganizationOnce(orgName); }, this.config.orgSelectionTimeout); } async retry(fn, timeout, maxAttempts = 3) { // Retry mechanism implementation } }tests/ui-testing/pages/ingestionPage.js (1)
7-7: Add JSDoc documentation for the ingestion method.Add documentation to describe the method's purpose, parameters, return value, and possible errors.
+/** + * Ingests test data into the specified stream. + * @returns {Promise<void>} A promise that resolves when ingestion is complete + * @throws {Error} If ingestion fails or response is invalid + */ async ingestion() {tests/ui-testing/pages/reportsPage.js (3)
9-10: Improve locator specificity for menu items.The menu locators could be more specific to ensure reliable test execution.
- this.homeMenu = page.locator("[name ='home']"); - this.reportsMenu = page.locator("[name='reports']"); + this.homeMenu = page.locator("button[name='home']"); + this.reportsMenu = page.locator("button[name='reports']");
29-32: Add visibility checks for more reliable navigation.The navigation could be more reliable by ensuring elements are visible before interaction.
async navigateToReports() { + await this.homeMenu.waitFor({ state: 'visible' }); await this.homeMenu.hover(); + await this.reportsMenu.waitFor({ state: 'visible' }); await this.reportsMenu.click(); + await this.scheduledTab.waitFor({ state: 'visible' }); await this.scheduledTab.click(); }
Line range hint
77-81: Replace hardcoded timeout in setDateTime method.Similar to other timeouts, replace the hardcoded wait with a more appropriate wait condition.
async setDateTime() { await expect(this.page.locator(this.dateTimeButton)).toBeVisible(); await this.page.locator(this.dateTimeButton).click(); await this.page.locator(this.absoluteTab).click(); - await this.page.waitForTimeout(1000); + await this.page.waitForLoadState('networkidle'); }tests/ui-testing/pages/dashboardPage.js (1)
70-71: Enhance deletion verification.While checking for the success message is good, consider adding a verification that the dashboard is actually removed from the list.
await this.page.locator('[data-test="confirm-button"]:visible').click(); await expect(this.page.getByRole('alert')).toContainText('Dashboard deleted successfully.'); +await expect(this.page.locator(`text=${this.dashboardName}`)).toHaveCount(0, { timeout: 5000 });
📜 Review details
Configuration used: CodeRabbit UI
Review profile: CHILL
📒 Files selected for processing (5)
tests/ui-testing/pages/dashboardPage.js(1 hunks)tests/ui-testing/pages/ingestionPage.js(1 hunks)tests/ui-testing/pages/loginPage.js(1 hunks)tests/ui-testing/pages/reportsPage.js(2 hunks)tests/ui-testing/playwright-tests/reportsUpdated.spec.js(1 hunks)
🚧 Files skipped from review as they are similar to previous changes (1)
- tests/ui-testing/playwright-tests/reportsUpdated.spec.js
🧰 Additional context used
📓 Path-based instructions (4)
tests/ui-testing/pages/dashboardPage.js (1)
Pattern **/*.js: You are a smart javascript/typescript pull request reviewer.
You are going to review all the javascript/typescript files.
Be concise, and add a brief explanation to your suggestions
Make sure the responses are not too verbose and keep the suggestions to the point i.e. actionable.
tests/ui-testing/pages/ingestionPage.js (1)
Pattern **/*.js: You are a smart javascript/typescript pull request reviewer.
You are going to review all the javascript/typescript files.
Be concise, and add a brief explanation to your suggestions
Make sure the responses are not too verbose and keep the suggestions to the point i.e. actionable.
tests/ui-testing/pages/loginPage.js (1)
Pattern **/*.js: You are a smart javascript/typescript pull request reviewer.
You are going to review all the javascript/typescript files.
Be concise, and add a brief explanation to your suggestions
Make sure the responses are not too verbose and keep the suggestions to the point i.e. actionable.
tests/ui-testing/pages/reportsPage.js (1)
Pattern **/*.js: You are a smart javascript/typescript pull request reviewer.
You are going to review all the javascript/typescript files.
Be concise, and add a brief explanation to your suggestions
Make sure the responses are not too verbose and keep the suggestions to the point i.e. actionable.
🪛 Biome
tests/ui-testing/pages/ingestionPage.js
[error] 26-26: Change to an optional chain.
Unsafe fix: Change to an optional chain.
(lint/complexity/useOptionalChain)
🔇 Additional comments (6)
tests/ui-testing/pages/loginPage.js (1)
8-8: LGTM! Good selector choice.
Using data-cy attribute for test selectors is a good practice as it provides a stable, test-specific locator.
tests/ui-testing/pages/ingestionPage.js (2)
1-6: LGTM! Clean class structure and proper initialization.
The import statement and class declaration follow best practices, with proper constructor initialization.
4-6: Verify the unused page parameter.
The constructor accepts a page parameter but it's not used in the class. Either remove it if unnecessary or implement the intended functionality.
tests/ui-testing/pages/reportsPage.js (2)
38-39: Replace hardcoded timeout with appropriate wait.
The previous review comment about replacing the 5000ms timeout is still valid.
58-66: LGTM! Well-structured verification and deletion methods.
The methods use appropriate assertions and handle dynamic content well.
tests/ui-testing/pages/dashboardPage.js (1)
2-15: LGTM! Well-structured class properties and imports.
The code follows good practices with:
- Clear imports from CommonLocator.js
- Consistent use of data-test attributes for locators
- Unique dashboard naming using timestamps
| async login() { | ||
| await this.userIdInput.fill(process.env["ZO_ROOT_USER_EMAIL"]); | ||
| const waitForLogin = this.page.waitForResponse( | ||
| (response) => | ||
| response.url().includes("/auth/login") && response.status() === 200 | ||
| ); | ||
| await this.passwordInput.fill(process.env["ZO_ROOT_USER_PASSWORD"]); | ||
| await this.waitForLogin; | ||
| await this.loginButton.click(); | ||
| await this.page.waitForURL(process.env["ZO_BASE_URL"] + "/web/", { | ||
| waitUntil: "networkidle", | ||
| }); | ||
| await this.page | ||
| .locator('[data-test="navbar-organizations-select"]') | ||
| .getByText("arrow_drop_down") | ||
| .click(); | ||
| await this.page.getByRole("option", { name: "default", exact: true }).click(); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Fix critical issues in login implementation.
Several issues need to be addressed:
- Incorrect promise variable usage on line 20 (
this.waitForLogindoesn't exist) - The login API response wait should happen after clicking the login button
- Missing environment variable validation
- Organization selection needs proper wait conditions
Apply these fixes:
async login() {
+ // Validate environment variables
+ const email = process.env["ZO_ROOT_USER_EMAIL"];
+ const password = process.env["ZO_ROOT_USER_PASSWORD"];
+ if (!email || !password) {
+ throw new Error('Missing required environment variables: ZO_ROOT_USER_EMAIL or ZO_ROOT_USER_PASSWORD');
+ }
- await this.userIdInput.fill(process.env["ZO_ROOT_USER_EMAIL"]);
+ await this.userIdInput.fill(email);
+ await this.passwordInput.fill(password);
const waitForLogin = this.page.waitForResponse(
(response) =>
response.url().includes("/auth/login") && response.status() === 200
);
- await this.passwordInput.fill(process.env["ZO_ROOT_USER_PASSWORD"]);
- await this.waitForLogin;
await this.loginButton.click();
+ await waitForLogin;
await this.page.waitForURL(process.env["ZO_BASE_URL"] + "/web/", {
waitUntil: "networkidle",
});
+ // Wait for organization dropdown to be visible
+ await this.page.locator('[data-test="navbar-organizations-select"]')
+ .waitFor({ state: 'visible' });
await this.page
.locator('[data-test="navbar-organizations-select"]')
.getByText("arrow_drop_down")
.click();
+ // Wait for organization option to be visible
+ await this.page.getByRole("option", { name: "default", exact: true })
+ .waitFor({ state: 'visible' });
await this.page.getByRole("option", { name: "default", exact: true }).click();
}📝 Committable suggestion
‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.
| async login() { | |
| await this.userIdInput.fill(process.env["ZO_ROOT_USER_EMAIL"]); | |
| const waitForLogin = this.page.waitForResponse( | |
| (response) => | |
| response.url().includes("/auth/login") && response.status() === 200 | |
| ); | |
| await this.passwordInput.fill(process.env["ZO_ROOT_USER_PASSWORD"]); | |
| await this.waitForLogin; | |
| await this.loginButton.click(); | |
| await this.page.waitForURL(process.env["ZO_BASE_URL"] + "/web/", { | |
| waitUntil: "networkidle", | |
| }); | |
| await this.page | |
| .locator('[data-test="navbar-organizations-select"]') | |
| .getByText("arrow_drop_down") | |
| .click(); | |
| await this.page.getByRole("option", { name: "default", exact: true }).click(); | |
| async login() { | |
| // Validate environment variables | |
| const email = process.env["ZO_ROOT_USER_EMAIL"]; | |
| const password = process.env["ZO_ROOT_USER_PASSWORD"]; | |
| if (!email || !password) { | |
| throw new Error('Missing required environment variables: ZO_ROOT_USER_EMAIL or ZO_ROOT_USER_PASSWORD'); | |
| } | |
| await this.userIdInput.fill(email); | |
| await this.passwordInput.fill(password); | |
| const waitForLogin = this.page.waitForResponse( | |
| (response) => | |
| response.url().includes("/auth/login") && response.status() === 200 | |
| ); | |
| await this.loginButton.click(); | |
| await waitForLogin; | |
| await this.page.waitForURL(process.env["ZO_BASE_URL"] + "/web/", { | |
| waitUntil: "networkidle", | |
| }); | |
| // Wait for organization dropdown to be visible | |
| await this.page.locator('[data-test="navbar-organizations-select"]') | |
| .waitFor({ state: 'visible' }); | |
| await this.page | |
| .locator('[data-test="navbar-organizations-select"]') | |
| .getByText("arrow_drop_down") | |
| .click(); | |
| // Wait for organization option to be visible | |
| await this.page.getByRole("option", { name: "default", exact: true }) | |
| .waitFor({ state: 'visible' }); | |
| await this.page.getByRole("option", { name: "default", exact: true }).click(); | |
| } |
| async ingestion() { | ||
| const orgId = process.env["ORGNAME"]; | ||
| const streamName = "e2e_automate"; | ||
| const basicAuthCredentials = Buffer.from(`${process.env["ZO_ROOT_USER_EMAIL"]}:${process.env["ZO_ROOT_USER_PASSWORD"]}`).toString('base64'); | ||
| const headers = { | ||
| "Authorization": `Basic ${basicAuthCredentials}`, | ||
| "Content-Type": "application/json", | ||
| }; | ||
| const fetchResponse = await fetch( | ||
| `${process.env.INGESTION_URL}/api/${orgId}/${streamName}/_json`, | ||
| { | ||
| method: "POST", | ||
| headers: headers, | ||
| body: JSON.stringify(logsdata), | ||
| } | ||
| ); | ||
| try { | ||
| const response = await fetchResponse; | ||
| const contentType = response.headers.get("content-type"); | ||
| if (contentType && contentType.includes("application/json")) { | ||
| const jsonData = await response.json(); | ||
| // Process JSON data here | ||
| } else { | ||
| const textData = await response.text(); | ||
| console.log("Response is not JSON:", textData); | ||
| // Handle the non-JSON response here | ||
| } | ||
| } catch (error) { | ||
| console.error("Failed to parse JSON response:", error); | ||
| } | ||
| } |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
🛠️ Refactor suggestion
Implement proper return value and error propagation.
The method should return a value indicating success/failure and propagate errors to the caller for better test flow control.
async ingestion() {
const orgId = process.env["ORGNAME"];
const streamName = "e2e_automate";
const basicAuthCredentials = Buffer.from(
`${process.env["ZO_ROOT_USER_EMAIL"]}:${process.env["ZO_ROOT_USER_PASSWORD"]}`
).toString('base64');
const headers = {
"Authorization": `Basic ${basicAuthCredentials}`,
"Content-Type": "application/json",
};
+ try {
const fetchResponse = await fetch(
`${process.env.INGESTION_URL}/api/${orgId}/${streamName}/_json`,
{
method: "POST",
headers: headers,
body: JSON.stringify(logsdata),
}
);
- try {
- const response = await fetchResponse;
- const contentType = response.headers.get("content-type");
- if (contentType && contentType.includes("application/json")) {
- const jsonData = await response.json();
- // Process JSON data here
- } else {
- const textData = await response.text();
- console.log("Response is not JSON:", textData);
- // Handle the non-JSON response here
- }
- } catch (error) {
- console.error("Failed to parse JSON response:", error);
+ if (!fetchResponse.ok) {
+ throw new Error(`Ingestion failed with status: ${fetchResponse.status}`);
}
+
+ const contentType = fetchResponse.headers?.get("content-type");
+ if (!contentType?.includes("application/json")) {
+ const textData = await fetchResponse.text();
+ throw new Error(`Invalid response type: ${contentType}, body: ${textData}`);
+ }
+
+ const response = await fetchResponse.json();
+ return {
+ success: true,
+ streamName,
+ response
+ };
+ } catch (error) {
+ throw new Error(`Ingestion failed: ${error.message}`);
}
}📝 Committable suggestion
‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.
| async ingestion() { | |
| const orgId = process.env["ORGNAME"]; | |
| const streamName = "e2e_automate"; | |
| const basicAuthCredentials = Buffer.from(`${process.env["ZO_ROOT_USER_EMAIL"]}:${process.env["ZO_ROOT_USER_PASSWORD"]}`).toString('base64'); | |
| const headers = { | |
| "Authorization": `Basic ${basicAuthCredentials}`, | |
| "Content-Type": "application/json", | |
| }; | |
| const fetchResponse = await fetch( | |
| `${process.env.INGESTION_URL}/api/${orgId}/${streamName}/_json`, | |
| { | |
| method: "POST", | |
| headers: headers, | |
| body: JSON.stringify(logsdata), | |
| } | |
| ); | |
| try { | |
| const response = await fetchResponse; | |
| const contentType = response.headers.get("content-type"); | |
| if (contentType && contentType.includes("application/json")) { | |
| const jsonData = await response.json(); | |
| // Process JSON data here | |
| } else { | |
| const textData = await response.text(); | |
| console.log("Response is not JSON:", textData); | |
| // Handle the non-JSON response here | |
| } | |
| } catch (error) { | |
| console.error("Failed to parse JSON response:", error); | |
| } | |
| } | |
| async ingestion() { | |
| const orgId = process.env["ORGNAME"]; | |
| const streamName = "e2e_automate"; | |
| const basicAuthCredentials = Buffer.from( | |
| `${process.env["ZO_ROOT_USER_EMAIL"]}:${process.env["ZO_ROOT_USER_PASSWORD"]}` | |
| ).toString('base64'); | |
| const headers = { | |
| "Authorization": `Basic ${basicAuthCredentials}`, | |
| "Content-Type": "application/json", | |
| }; | |
| try { | |
| const fetchResponse = await fetch( | |
| `${process.env.INGESTION_URL}/api/${orgId}/${streamName}/_json`, | |
| { | |
| method: "POST", | |
| headers: headers, | |
| body: JSON.stringify(logsdata), | |
| } | |
| ); | |
| if (!fetchResponse.ok) { | |
| throw new Error(`Ingestion failed with status: ${fetchResponse.status}`); | |
| } | |
| const contentType = fetchResponse.headers?.get("content-type"); | |
| if (!contentType?.includes("application/json")) { | |
| const textData = await fetchResponse.text(); | |
| throw new Error(`Invalid response type: ${contentType}, body: ${textData}`); | |
| } | |
| const response = await fetchResponse.json(); | |
| return { | |
| success: true, | |
| streamName, | |
| response | |
| }; | |
| } catch (error) { | |
| throw new Error(`Ingestion failed: ${error.message}`); | |
| } | |
| } |
🧰 Tools
🪛 Biome
[error] 26-26: Change to an optional chain.
Unsafe fix: Change to an optional chain.
(lint/complexity/useOptionalChain)
| await this.folderInput.dblclick(); | ||
| await this.page.waitForLoadState("networkidle"); | ||
| await this.folderInput.pressSequentially('de', { delay: 100 }); | ||
| await this.page.getByRole('option', { name: 'default' }).click(); | ||
| await this.dashboardInput.dblclick(); | ||
| await this.page.waitForLoadState("networkidle"); | ||
| await this.dashboardInput.pressSequentially(dashboardName, { delay: 100 }); | ||
| await this.page.getByRole('option', { name: dashboardName }).click(); | ||
| await this.dashboardTabInput.dblclick(); | ||
| await this.page.waitForLoadState("networkidle"); | ||
| await this.dashboardTabInput.pressSequentially('de', { delay: 100 }); | ||
| await this.page.getByRole('option', { name: 'default' }).click(); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
🛠️ Refactor suggestion
Simplify input field interactions.
The current implementation uses unnecessary double-clicks and the deprecated pressSequentially method.
- await this.folderInput.dblclick();
- await this.page.waitForLoadState("networkidle");
- await this.folderInput.pressSequentially('de', { delay: 100 });
+ await this.folderInput.click();
+ await this.folderInput.type('de');
- await this.dashboardInput.dblclick();
- await this.page.waitForLoadState("networkidle");
- await this.dashboardInput.pressSequentially(dashboardName, { delay: 100 });
+ await this.dashboardInput.click();
+ await this.dashboardInput.type(dashboardName);
- await this.dashboardTabInput.dblclick();
- await this.page.waitForLoadState("networkidle");
- await this.dashboardTabInput.pressSequentially('de', { delay: 100 });
+ await this.dashboardTabInput.click();
+ await this.dashboardTabInput.type('de');📝 Committable suggestion
‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.
| await this.folderInput.dblclick(); | |
| await this.page.waitForLoadState("networkidle"); | |
| await this.folderInput.pressSequentially('de', { delay: 100 }); | |
| await this.page.getByRole('option', { name: 'default' }).click(); | |
| await this.dashboardInput.dblclick(); | |
| await this.page.waitForLoadState("networkidle"); | |
| await this.dashboardInput.pressSequentially(dashboardName, { delay: 100 }); | |
| await this.page.getByRole('option', { name: dashboardName }).click(); | |
| await this.dashboardTabInput.dblclick(); | |
| await this.page.waitForLoadState("networkidle"); | |
| await this.dashboardTabInput.pressSequentially('de', { delay: 100 }); | |
| await this.page.getByRole('option', { name: 'default' }).click(); | |
| await this.folderInput.click(); | |
| await this.folderInput.type('de'); | |
| await this.page.getByRole('option', { name: 'default' }).click(); | |
| await this.dashboardInput.click(); | |
| await this.dashboardInput.type(dashboardName); | |
| await this.page.getByRole('option', { name: dashboardName }).click(); | |
| await this.dashboardTabInput.click(); | |
| await this.dashboardTabInput.type('de'); | |
| await this.page.getByRole('option', { name: 'default' }).click(); |
| await this.page.locator('label').filter({ hasText: 'Streamarrow_drop_down' }).locator('i').click(); | ||
| // Refine the locator for 'e2e_automate' | ||
| await this.page | ||
| .locator("span") | ||
| .filter({ hasText: /^e2e_automate$/ }) | ||
| .click(); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Improve stream selection reliability.
The current stream selection logic using text filters is fragile:
.filter({ hasText: 'Streamarrow_drop_down' })
.filter({ hasText: /^e2e_automate$/ })Consider using data attributes and environment variables:
- await this.page.locator('label').filter({ hasText: 'Streamarrow_drop_down' }).locator('i').click();
- await this.page.locator("span").filter({ hasText: /^e2e_automate$/ }).click();
+ const streamName = process.env.TEST_STREAM_NAME || 'e2e_automate';
+ await this.page.locator('[data-test="stream-selector"]').click();
+ await this.page.locator(`[data-test="stream-option-${streamName}"]`).click();| await this.page | ||
| .locator( | ||
| '[data-test="field-list-item-logs-e2e_automate-kubernetes_container_hash"] [data-test="dashboard-add-y-data"]' | ||
| ) | ||
| .click(); | ||
| await this.page | ||
| .locator( | ||
| '[data-test="field-list-item-logs-e2e_automate-kubernetes_container_image"] [data-test="dashboard-add-b-data"]' | ||
| ) | ||
| .click(); | ||
| await this.page.waitForSelector('[data-test="dashboard-panel-name"]'); | ||
| await this.page.locator('[data-test="dashboard-panel-name"]').click(); | ||
| await this.page.locator('[data-test="dashboard-panel-name"]').fill('AutoP'); | ||
| await this.page.locator('[data-test="dashboard-panel-name"]').press('Enter'); | ||
| await expect(this.page.locator('[data-test="dashboard-apply"]')).toBeVisible(); | ||
| await this.page.locator('[data-test="dashboard-apply"]').click(); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
🛠️ Refactor suggestion
Simplify panel configuration logic.
The current implementation has repetitive locator patterns and could be simplified.
Consider extracting a helper method:
async addPanelField(streamName, fieldName, dataType) {
await this.page.locator(
`[data-test="field-list-item-logs-${streamName}-${fieldName}"] [data-test="dashboard-add-${dataType}-data"]`
).click();
}Usage:
- await this.page.locator('[data-test="field-list-item-logs-e2e_automate-kubernetes_container_hash"] [data-test="dashboard-add-y-data"]').click();
- await this.page.locator('[data-test="field-list-item-logs-e2e_automate-kubernetes_container_image"] [data-test="dashboard-add-b-data"]').click();
+ await this.addPanelField('e2e_automate', 'kubernetes_container_hash', 'y');
+ await this.addPanelField('e2e_automate', 'kubernetes_container_image', 'b');
test case report e2e
Summary by CodeRabbit
Release Notes
New Features
IngestionPageclass for handling data ingestion through an API.DashboardPagewith improved dashboard creation and deletion processes.ReportsPageto support report creation and verification with new methods.Bug Fixes
Documentation