Skip to content

Conversation

@ShyamOOAI
Copy link
Contributor

@ShyamOOAI ShyamOOAI commented Nov 4, 2024

test case report e2e

Summary by CodeRabbit

Release Notes

  • New Features

    • Added a new test suite for report functionalities, including creating and deleting dashboards and reports.
    • Introduced a new IngestionPage class for handling data ingestion through an API.
    • Enhanced DashboardPage with improved dashboard creation and deletion processes.
    • Updated ReportsPage to support report creation and verification with new methods.
  • Bug Fixes

    • Improved element selection methods for better reliability in the login process.
  • Documentation

    • Streamlined import statements and improved code organization for better readability.

@coderabbitai
Copy link
Contributor

coderabbitai bot commented Nov 4, 2024

Walkthrough

The pull request introduces modifications to the Playwright testing framework and UI testing classes. It updates the workflow to include a new test file, reportsUpdated.spec.js, enhances the DashboardPage, IngestionPage, LoginPage, and ReportsPage classes with new methods and properties, and refines locator strategies. The changes improve dashboard creation, report management, and the overall testing process, focusing on utilizing environment variables and enhancing interaction with UI elements.

Changes

File Change Summary
.github/workflows/playwright.yml Updated ui_integration_tests to include reportsUpdated.spec.js and improved test filename formatting.
tests/ui-testing/pages/dashboardPage.js Added dashboardName property, updated locators, renamed addDashboard to createDashboard, introduced deleteDashboard method.
tests/ui-testing/pages/ingestionPage.js Introduced new file defining IngestionPage class with ingestion method for API data ingestion.
tests/ui-testing/pages/loginPage.js Modified login method to use environment variables, updated locator for login button.
tests/ui-testing/pages/reportsPage.js Renamed addNewReport to createReport, added verifyReportSaved and deleteReport methods, expanded locators.
tests/ui-testing/playwright-tests/reportsUpdated.spec.js Added new test suite and test case for dashboard and report functionalities.

Possibly related PRs

Suggested labels

✏️ Feature

Suggested reviewers

  • neha00290

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?

❤️ Share
🪧 Tips

Chat

There are 3 ways to chat with CodeRabbit:

  • Review comments: Directly reply to a review comment made by CodeRabbit. Example:
    • I pushed a fix in commit <commit_id>, please review it.
    • Generate unit testing code for this file.
    • Open a follow-up GitHub issue for this discussion.
  • Files and specific lines of code (under the "Files changed" tab): Tag @coderabbitai in a new review comment at the desired location with your query. Examples:
    • @coderabbitai generate unit testing code for this file.
    • @coderabbitai modularize this function.
  • PR comments: Tag @coderabbitai in a new PR comment to ask questions about the PR branch. For the best results, please provide a very specific query, as very limited context is provided in this mode. Examples:
    • @coderabbitai gather interesting stats about this repository and render them as a table. Additionally, render a pie chart showing the language distribution in the codebase.
    • @coderabbitai read src/utils.ts and generate unit testing code.
    • @coderabbitai read the files in the src/scheduler package and generate a class diagram using mermaid and a README in the markdown format.
    • @coderabbitai help me debug CodeRabbit configuration file.

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)

  • @coderabbitai pause to pause the reviews on a PR.
  • @coderabbitai resume to resume the paused reviews.
  • @coderabbitai review to trigger an incremental review. This is useful when automatic reviews are disabled for the repository.
  • @coderabbitai full review to do a full review from scratch and review all the files again.
  • @coderabbitai summary to regenerate the summary of the PR.
  • @coderabbitai resolve resolve all the CodeRabbit review comments.
  • @coderabbitai configuration to show the current CodeRabbit configuration for the repository.
  • @coderabbitai help to get help.

Other keywords and placeholders

  • Add @coderabbitai ignore anywhere in the PR description to prevent this PR from being reviewed.
  • Add @coderabbitai summary to generate the high-level summary at a specific location in the PR description.
  • Add @coderabbitai anywhere in the PR title to generate the title automatically.

CodeRabbit Configuration File (.coderabbit.yaml)

  • You can programmatically configure CodeRabbit by adding a .coderabbit.yaml file to the root of your repository.
  • Please see the configuration documentation for more information.
  • If your editor has YAML language server enabled, you can add the path at the top of this file to enable auto-completion and validation: # yaml-language-server: $schema=https://coderabbit.ai/integrations/schema.v2.json

Documentation and Community

  • Visit our Documentation for detailed information on how to use CodeRabbit.
  • Join our Discord Community to get help, request features, and share feedback.
  • Follow us on X/Twitter for updates and announcements.

Copy link
Contributor

@coderabbitai coderabbitai bot left a 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-cy attribute 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:

  1. Consider using a consistent indentation level
  2. Add commas consistently after each entry
  3. 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:

  1. Remove trailing spaces
  2. Fix indentation to be consistent (11 spaces)
  3. 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, dashboardName is set to dashboard${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 waitForTimeout with 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 waitForTimeout with 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 fields

Hardcoding values like "rreport1" and "reporterTest" reduces code flexibility. Consider passing these values as parameters or using constants.

Refactor the code to accept reportName and reportTitle as 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 clarity

The 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

📥 Commits

Reviewing files that changed from the base of the PR and between 4603b26 and d291c1e.

📒 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}."
fi
tests/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.

Comment on lines 17 to 25
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();

Copy link
Contributor

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:

  1. Add validation for required environment variables
  2. Remove redundant waitForSelector calls as Playwright's fill() already includes implicit waiting
  3. 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.

Suggested change
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();
}

Comment on lines 11 to 22
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();

});
Copy link
Contributor

@coderabbitai coderabbitai bot Nov 4, 2024

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:

  1. Using Playwright's built-in wait conditions like waitForLoadState or waitForSelector
  2. 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.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

have removed waitForTimeout

Copy link
Contributor

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!

Comment on lines 24 to 41
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();
});
Copy link
Contributor

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.

  1. Remove commented out code
  2. Add assertions to verify operations
  3. Move URL construction to page objects
  4. 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:

  1. Dashboard creation and deletion
  2. 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.

Suggested change
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();
});

Comment on lines 9 to 27
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();
Copy link
Contributor

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:

  1. Environment variables should be validated before use
  2. Authentication logic should be extracted to a separate method
  3. 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.

Comment on lines 19 to 26
const fetchResponse = await fetch(
`${process.env.INGESTION_URL}/api/${orgId}/${streamName}/_json`,
{
method: "POST",
headers: headers,
body: JSON.stringify(logsdata),
}
);

Copy link
Contributor

Choose a reason for hiding this comment

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

⚠️ Potential issue

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.

Suggested change
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;
}

Comment on lines 30 to 48
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);
}
Copy link
Contributor

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:

  1. Remove redundant response assignment
  2. Replace console.log with proper logging
  3. 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.

Suggested change
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)

Comment on lines +29 to 31
await this.page.waitForSelector('[data-test="menu-link-\\/dashboards-item"]');
await this.dashboardsMenuItem.click();
await this.page.waitForTimeout(5000);
Copy link
Contributor

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

Comment on lines 68 to 121

//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.');


Copy link
Contributor

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.

Suggested change
//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.');

Comment on lines 53 to 55
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();
Copy link
Contributor

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 });
Copy link
Contributor

Choose a reason for hiding this comment

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

⚠️ Potential issue

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

Copy link
Contributor

@coderabbitai coderabbitai bot left a 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 logic

The 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 code

Remove 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

📥 Commits

Reviewing files that changed from the base of the PR and between d291c1e and 1ae35a9.

📒 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:

  1. The environment variable ORGNAME is used consistently across test files
  2. The login page implementation explicitly selects "default" organization after login
  3. The test files use org_identifier parameter with ORGNAME in URLs
  4. 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

Comment on lines 16 to 39
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();
Copy link
Contributor

Choose a reason for hiding this comment

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

⚠️ Potential issue

Fix promise handling and improve test reliability.

The login sequence has several issues that could affect test reliability:

  1. The waitForLogin promise is created but used after filling the password, which could miss the API call
  2. Organization selection might be flaky under slow network conditions
  3. 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.

Suggested change
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";
Copy link
Contributor

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:

  1. Making it configurable via environment variable
  2. 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.

Suggested change
const streamName = "e2e_automate";
const streamName = `e2e_automate_${process.env.TEST_ID || Date.now()}`;

Comment on lines 38 to 105
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);

Copy link
Contributor

@coderabbitai coderabbitai bot Nov 4, 2024

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,
  });
}

Copy link
Contributor

Choose a reason for hiding this comment

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

please remove extra lines

Copy link
Contributor

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.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

have removed extra lines

Copy link
Contributor

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!

Copy link
Contributor

@coderabbitai coderabbitai bot left a 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:

  1. Line 27: Commented out response parsing
  2. Line 36: TODO for JSON data processing
  3. Line 40: TODO for non-JSON response handling
  4. 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

📥 Commits

Reviewing files that changed from the base of the PR and between 1ae35a9 and 9cfac94.

📒 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: ⚠️ Potential issue

Fix critical issues in login implementation.

There are several issues that need to be addressed:

  1. Line 30: Using this.waitForLogin instead of the local waitForLogin constant will cause undefined behavior
  2. 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.

Comment on lines 29 to 44
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);
}
Copy link
Contributor

Choose a reason for hiding this comment

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

⚠️ Potential issue

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.

Suggested change
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)

Comment on lines +54 to +55
await this.reportNameInput.fill("rreport1");
await this.page.waitForTimeout(5000);
Copy link
Contributor

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.

Suggested change
await this.reportNameInput.fill("rreport1");
await this.page.waitForTimeout(5000);
await this.reportNameInput.fill("rreport1");
await this.page.waitForLoadState("networkidle");

Comment on lines 41 to 47
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();
Copy link
Contributor

Choose a reason for hiding this comment

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

⚠️ Potential issue

Fix incomplete waitForSelector calls and remove commented code.

The navigation method has several issues:

  1. Incomplete waitForSelector calls that will cause runtime errors
  2. 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.

Suggested change
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();

Comment on lines 57 to 74
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();
Copy link
Contributor

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:

  1. Text-based filtering with 'Streamarrow_drop_down'
  2. 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 });
Copy link
Contributor

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.

Copy link
Contributor

@coderabbitai coderabbitai bot left a 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:

  1. Split the organization selection into a separate method for better separation of concerns
  2. Add retry mechanisms for flaky UI operations
  3. 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

📥 Commits

Reviewing files that changed from the base of the PR and between 9cfac94 and aeef384.

📒 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

Comment on lines +13 to +29
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();
Copy link
Contributor

Choose a reason for hiding this comment

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

⚠️ Potential issue

Fix critical issues in login implementation.

Several issues need to be addressed:

  1. Incorrect promise variable usage on line 20 (this.waitForLogin doesn't exist)
  2. The login API response wait should happen after clicking the login button
  3. Missing environment variable validation
  4. 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.

Suggested change
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();
}

Comment on lines +7 to +37
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);
}
}
Copy link
Contributor

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.

Suggested change
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)

Comment on lines +40 to +51
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();
Copy link
Contributor

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.

Suggested change
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();

Comment on lines +42 to +47
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();
Copy link
Contributor

Choose a reason for hiding this comment

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

⚠️ Potential issue

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();

Comment on lines +48 to +63
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();
Copy link
Contributor

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');

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants