Skip to content
New issue

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

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

Already on GitHub? Sign in to your account

HP-2382 Migrate PartCreateCest.php Codeception Test to Playwright and remove Legacy Test #185

Conversation

VadymHrechukha
Copy link
Contributor

@VadymHrechukha VadymHrechukha commented Mar 3, 2025

Summary by CodeRabbit

  • New Features

    • Enhanced parts management experience: Users can now create, duplicate, remove, and replace parts with an improved, more intuitive workflow.
    • Clearer feedback during part creation: The system now provides timely error notifications when required information is missing.
    • Upgraded pricing inputs: Pricing fields now include integrated currency support for more accurate and consistent data entry.
    • New functionality for selecting and confirming part replacements with improved navigation and filtering options.
    • New form interactions for filling and saving part replacement details efficiently.
    • Improved interaction with part details through a refined user interface.
    • New functionality for selling parts, including a dedicated sell modal for streamlined transactions.
    • New bill management interface to track and verify sales transactions.
  • Bug Fixes

    • Adjusted spacing in the part detail menu for better visual alignment.
  • Tests

    • New test coverage for part management features, ensuring reliability and correctness of operations.
    • New test cases added to validate part sales functionality and ensure proper handling of sales data.

Copy link

coderabbitai bot commented Mar 3, 2025

Warning

Rate limit exceeded

@VadymHrechukha has exceeded the limit for the number of commits or files that can be reviewed per hour. Please wait 15 minutes and 14 seconds before requesting another review.

⌛ How to resolve this issue?

After the wait time has elapsed, a review can be triggered using the @coderabbitai review command as a PR comment. Alternatively, push new commits to this PR.

We recommend that you space out your commits to avoid hitting the rate limit.

🚦 How do rate limits work?

CodeRabbit enforces hourly rate limits for each developer per organization.

Our paid plans have higher rate limits than the trial, open-source and free plans. In all cases, we re-allow further reviews after a brief timeout.

Please see our FAQ for further information.

📥 Commits

Reviewing files that changed from the base of the PR and between 5595d68 and 0a731cd.

📒 Files selected for processing (3)
  • tests/acceptance/manager/part/PartCreateCest.php (0 hunks)
  • tests/acceptance/manager/part/PartReplaceCest.php (0 hunks)
  • tests/acceptance/manager/part/PartSellCest.php (0 hunks)

Walkthrough

This changeset introduces new Playwright page objects and end-to-end test suites for part management. New classes, PartCreateView, PartIndexView, PartReplaceView, PartForm, PartView, SellModal, and BillIndexView encapsulate UI interactions for creating, managing, replacing, and selling parts. Additionally, the PriceWithCurrency class provides a utility for handling currency-based inputs. Complementary test suites validate functionalities such as part creation, copying, deletion, replacement, and selling, including filter application and error handling.

Changes

File(s) Change Summary
tests/playwright/page/PartCreateView.ts, tests/playwright/page/PartIndexView.ts, tests/playwright/page/PartReplaceView.ts, tests/playwright/helper/PartForm.ts, tests/playwright/page/PartView.ts, tests/playwright/page/SellModal.ts, tests/playwright/page/BillIndexView.ts Introduces page object classes for part creation, management, replacement, and selling with methods for navigation, form manipulation, and bulk actions.
tests/playwright/e2e/manager/part/part-create.spec.ts, tests/playwright/e2e/manager/part/part-replace.spec.ts, tests/playwright/e2e/manager/part/part-sell.spec.ts Adds end-to-end test suites for verifying part creation (with add, copy, remove, and deletion flows), part replacement, and selling functionalities using UI interactions and error validations.
tests/playwright/input/PriceWithCurrency.ts Introduces the PriceWithCurrency class extending InputWithCurrency, including a static field method for generating unique price input instances.
src/views/part/view.php Modifies the linkTemplate parameter in the PartDetailMenu::widget call to remove a non-breaking space between the icon and the label.

Sequence Diagram(s)

sequenceDiagram
    participant T as Test Suite
    participant PCV as PartCreateView
    participant UI as Browser Page

    T ->> PCV: navigate()
    PCV ->> UI: Open part creation page
    T ->> PCV: fillPartFields(data)
    PCV ->> UI: Populate form fields with data
    T ->> PCV: addPart()/copyPart()/removePart()
    PCV ->> UI: Perform corresponding button clicks
    T ->> PCV: save()
    PCV ->> UI: Click 'Save' button and submit form
Loading
sequenceDiagram
    participant T as Test Suite
    participant PIV as PartIndexView
    participant UI as Browser Page

    T ->> PIV: navigate()
    PIV ->> UI: Open part index page
    T ->> PIV: applyFilters(filters)
    PIV ->> UI: Set filters and submit search
    T ->> PIV: selectPartsToReplace(count)
    PIV ->> UI: Select table rows for replacement
    T ->> PIV: confirmReplacement()
    PIV ->> UI: Click bulk 'Replace' button and verify notification
Loading
sequenceDiagram
    participant T as Test Suite
    participant SM as SellModal
    participant UI as Browser Page

    T ->> SM: fillSellWindowFields(data)
    SM ->> UI: Populate sell modal fields
    T ->> SM: confirmSale()
    SM ->> UI: Click 'Sell' button
    T ->> SM: seePartsWereSold()
    SM ->> UI: Verify sale confirmation notification
Loading

Poem

Oh, what a hop it is in code land,
Parts and tests all neatly planned.
I scurry through views with a joyful beat,
Filling forms and clicking buttons so sweet.
With each change, I dance—code carrots at hand!
🐇🥕


Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ 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 generate docstrings to generate docstrings for this 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

@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

🧹 Nitpick comments (7)
tests/playwright/page/PartCreateView.ts (3)

1-1: Remove unused import

The expect import is not used in this class. It's good practice to keep imports clean.

-import {expect, Locator, Page} from "@playwright/test";
+import {Locator, Page} from "@playwright/test";

14-23: Replace any type with proper interface and add validation checks

Using any reduces type safety. Define a proper interface for partData and consider adding validation or waiting for elements before interactions.

+interface PartData {
+    partno: string;
+    src_id: string;
+    dst_id: string;
+    serials: string;
+    move_descr: string;
+    price: number;
+    currency: string;
+    company_id: string;
+}

-public async fillPartFields(partData: any) {
+public async fillPartFields(partData: PartData) {
+    // Wait for form to be ready
+    await this.page.waitForSelector('select[id$=partno]');
    await this.page.selectOption('select[id$=partno]', partData.partno);
    await this.page.selectOption('select[id$=src_id]', partData.src_id);
    await this.page.selectOption('select[id$=dst_ids]', partData.dst_id);
    await this.page.fill('input[id$=serials]', partData.serials);
    await this.page.fill('input[id$=move_descr]', partData.move_descr);
    await this.page.fill('input[id$=price]', partData.price.toString());
-    await this.page.click(`li a[data-value=${partData.currency}]`);
+    await this.page.click(`li a[data-value="${partData.currency}"]`);
    await this.page.selectOption('select[id$=company_id]', partData.company_id);
}

25-39: Add wait conditions for more robust interaction

The current button interactions might fail if elements are not yet ready or visible. Adding wait conditions would make these methods more robust.

public async save() {
+    await this.page.waitForSelector('button:has-text("Save")');
    await this.page.click('button:has-text("Save")');
}

public async addPart() {
+    await this.page.waitForSelector("div.item:last-child button.add-item");
    await this.page.click("div.item:last-child button.add-item");
}

public async removePart() {
+    await this.page.waitForSelector("div.item:last-child button.remove-item");
    await this.page.click("div.item:last-child button.remove-item");
}

public async copyPart() {
+    await this.page.waitForSelector("div.item:last-child button.copy");
    await this.page.click("div.item:last-child button.copy");
}
tests/playwright/e2e/manager/part-create.spec.ts (4)

24-25: Avoid using side-effect operators in assertion messages

Using ++n inside an assertion call can be confusing as it modifies the variable while also using its updated value. This makes the code harder to understand.

await partView.addPart();
-expect(await managerPage.locator('div.item').count()).toBe(++n);
+n++;
+expect(await managerPage.locator('div.item').count()).toBe(n);

This comment applies to all similar occurrences throughout the file.


43-61: Extract error messages to constants and add descriptions to assertions

Extracting error messages to constants would make the test more maintainable and adding descriptions to assertions would make test failures more informative.

+const VALIDATION_ERRORS = {
+  partNo: 'Part No. cannot be blank.',
+  source: 'Source cannot be blank.',
+  destination: 'Destination cannot be blank.',
+  serials: 'Serials cannot be blank.',
+  moveDesc: 'Move description cannot be blank.',
+  price: 'Purchase price cannot be blank.',
+  currency: 'Currency cannot be blank.',
+};

test('Ensure part cannot be created without data @hipanel-module-stock @manager', async ({ managerPage }) => {
    const partView = new PartCreateView(managerPage);
    await partView.navigate();
    await partView.save();

-    const errorMessages = [
-        'Part No. cannot be blank.',
-        'Source cannot be blank.',
-        'Destination cannot be blank.',
-        'Serials cannot be blank.',
-        'Move description cannot be blank.',
-        'Purchase price cannot be blank.',
-        'Currency cannot be blank.',
-    ];
+    const errorMessages = Object.values(VALIDATION_ERRORS);

    for (const message of errorMessages) {
-        await expect(managerPage.locator(`text=${message}`)).toBeVisible();
+        await expect(managerPage.locator(`text=${message}`)).toBeVisible({
+          message: `Expected error message "${message}" to be visible`
+        });
    }
});

87-89: Add timeout to expectations for better error messages

Adding timeouts to expectations will provide clearer error messages if the operation takes longer than expected.

managerPage.on('dialog', async dialog => await dialog.accept());
await managerPage.click('text=Delete');
-await expect(managerPage.locator('text=Part has been deleted')).toBeVisible();
+await expect(managerPage.locator('text=Part has been deleted')).toBeVisible({
+  timeout: 5000,
+  message: 'Expected confirmation message for part deletion to appear'
+});

17-41: Add cleanup and simplify button interaction test

The test adds and removes parts several times, which is redundant. A simplified test with cleanup would be more maintainable.

test('Ensure part management buttons work @hipanel-module-stock @manager', async ({ managerPage }) => {
    const partView = new PartCreateView(managerPage);
    await partView.navigate();

    let n = await managerPage.locator('div.item').count();
    expect(n).toBe(1);

    // Test add button
    await partView.addPart();
    n++;
    expect(await managerPage.locator('div.item').count()).toBe(n);

    // Test copy button
    await partView.copyPart();
    n++;
    expect(await managerPage.locator('div.item').count()).toBe(n);

    // Test remove button
    await partView.removePart();
    n--;
    expect(await managerPage.locator('div.item').count()).toBe(n);
    
-    await partView.addPart();
-    expect(await managerPage.locator('div.item').count()).toBe(++n);
-
-    await partView.copyPart();
-    expect(await managerPage.locator('div.item').count()).toBe(++n);
-
-    await partView.removePart();
-    expect(await managerPage.locator('div.item').count()).toBe(--n);
-
-    await partView.removePart();
-    expect(await managerPage.locator('div.item').count()).toBe(--n);
-
-    await partView.removePart();
-    expect(await managerPage.locator('div.item').count()).toBe(--n);
+    
+    // Cleanup - remove all additional items
+    while (n > 1) {
+        await partView.removePart();
+        n--;
+        expect(await managerPage.locator('div.item').count()).toBe(n);
+    }
});
📜 Review details

Configuration used: CodeRabbit UI
Review profile: CHILL
Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 18048dc and 70ab712.

📒 Files selected for processing (2)
  • tests/playwright/e2e/manager/part-create.spec.ts (1 hunks)
  • tests/playwright/page/PartCreateView.ts (1 hunks)
🔇 Additional comments (1)
tests/playwright/page/PartCreateView.ts (1)

17-17:

✅ Verification successful

Verify selector and parameter name consistency

The selector uses dst_ids (plural) but the parameter is named dst_id (singular). Consider making these consistent to prevent future errors.


🏁 Script executed:

#!/bin/bash
# Searching for dst_ids fields in the codebase to verify correct naming
rg -A 2 "dst_ids" --type html --type php
rg -A 2 "dst_id" --type html --type php

Length of output: 12329


Selector and Parameter Naming Verified

After reviewing the codebase, it is confirmed that both dst_ids (used in selectors and model attributes) and dst_id (used for singular values in test data and controller transformations) are intentionally and consistently applied throughout the project. The design handles multiple destination IDs via the HTML element (dst_ids) while working with a single selected value (dst_id) where applicable. No changes are needed at this time.

await this.page.fill('input[id$=serials]', partData.serials);
await this.page.fill('input[id$=move_descr]', partData.move_descr);
await this.page.fill('input[id$=price]', partData.price.toString());
await this.page.click(`li a[data-value=${partData.currency}]`);
Copy link

Choose a reason for hiding this comment

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

🛠️ Refactor suggestion

Add string interpolation safety for currency selector

Use proper string interpolation with quotes to prevent potential XSS or injection issues when concatenating values into selectors.

-await this.page.click(`li a[data-value=${partData.currency}]`);
+await this.page.click(`li a[data-value="${partData.currency}"]`);
📝 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.click(`li a[data-value=${partData.currency}]`);
await this.page.click(`li a[data-value="${partData.currency}"]`);

Comment on lines 63 to 69
test('Ensure a part can be created @hipanel-module-stock @manager', async ({ managerPage }) => {
const partView = new PartCreateView(managerPage);
await partView.navigate();
await partView.fillPartFields(partData);
await partView.save();
await expect(managerPage.locator('text=Part has been created')).toBeVisible();
});
Copy link

Choose a reason for hiding this comment

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

🛠️ Refactor suggestion

Use dynamic test data for each test

Use the function suggested earlier to create unique test data for each test to prevent conflicts between tests.

test('Ensure a part can be created @hipanel-module-stock @manager', async ({ managerPage }) => {
    const partView = new PartCreateView(managerPage);
+    const uniquePartData = createPartData();
    await partView.navigate();
-    await partView.fillPartFields(partData);
+    await partView.fillPartFields(uniquePartData);
    await partView.save();
    await expect(managerPage.locator('text=Part has been created')).toBeVisible();
});
📝 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('Ensure a part can be created @hipanel-module-stock @manager', async ({ managerPage }) => {
const partView = new PartCreateView(managerPage);
await partView.navigate();
await partView.fillPartFields(partData);
await partView.save();
await expect(managerPage.locator('text=Part has been created')).toBeVisible();
});
test('Ensure a part can be created @hipanel-module-stock @manager', async ({ managerPage }) => {
const partView = new PartCreateView(managerPage);
const uniquePartData = createPartData();
await partView.navigate();
await partView.fillPartFields(uniquePartData);
await partView.save();
await expect(managerPage.locator('text=Part has been created')).toBeVisible();
});

Comment on lines 5 to 14
const partData = {
partno: 'CHASSIS EPYC 7402P',
src_id: 'TEST-DS-01',
dst_id: 'TEST-DS-02',
serials: `MG_TEST_PART${Date.now()}`,
move_descr: 'MG TEST MOVE',
price: 200,
currency: 'usd',
company_id: 'Other',
};
Copy link

Choose a reason for hiding this comment

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

🛠️ Refactor suggestion

Use dynamic serials for all tests to prevent collisions

The current implementation uses a timestamp for serials, but it's defined once at the top level. This can cause collisions if tests run quickly in sequence.

-const partData = {
+function createPartData() {
+  return {
    partno: 'CHASSIS EPYC 7402P',
    src_id: 'TEST-DS-01',
    dst_id: 'TEST-DS-02',
-    serials: `MG_TEST_PART${Date.now()}`,
+    serials: `MG_TEST_PART${Date.now()}_${Math.floor(Math.random() * 1000)}`,
    move_descr: 'MG TEST MOVE',
    price: 200,
    currency: 'usd',
    company_id: 'Other',
+  };
};
📝 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 partData = {
partno: 'CHASSIS EPYC 7402P',
src_id: 'TEST-DS-01',
dst_id: 'TEST-DS-02',
serials: `MG_TEST_PART${Date.now()}`,
move_descr: 'MG TEST MOVE',
price: 200,
currency: 'usd',
company_id: 'Other',
};
function createPartData() {
return {
partno: 'CHASSIS EPYC 7402P',
src_id: 'TEST-DS-01',
dst_id: 'TEST-DS-02',
serials: `MG_TEST_PART${Date.now()}_${Math.floor(Math.random() * 1000)}`,
move_descr: 'MG TEST MOVE',
price: 200,
currency: 'usd',
company_id: 'Other',
};
};

Comment on lines 80 to 90
test('Ensure a part can be created and then deleted @hipanel-module-stock @manager', async ({ managerPage }) => {
const partView = new PartCreateView(managerPage);
await partView.navigate();
await partView.fillPartFields(partData);
await partView.save();
await expect(managerPage.locator('text=Part has been created')).toBeVisible();

await managerPage.click('text=Delete');
managerPage.on('dialog', async dialog => await dialog.accept());
await expect(managerPage.locator('text=Part has been deleted')).toBeVisible();
});
Copy link

Choose a reason for hiding this comment

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

🛠️ Refactor suggestion

Register dialog handler before triggering dialog and use unique test data

The dialog handler should be registered before any action that might trigger a dialog, and each test should use unique data.

test('Ensure a part can be created and then deleted @hipanel-module-stock @manager', async ({ managerPage }) => {
    const partView = new PartCreateView(managerPage);
+    const uniquePartData = createPartData();
    await partView.navigate();
-    await partView.fillPartFields(partData);
+    await partView.fillPartFields(uniquePartData);
    await partView.save();
    await expect(managerPage.locator('text=Part has been created')).toBeVisible();

+    // Register dialog handler before clicking delete
+    managerPage.on('dialog', async dialog => await dialog.accept());
    await managerPage.click('text=Delete');
-    managerPage.on('dialog', async dialog => await dialog.accept());
    await expect(managerPage.locator('text=Part has been deleted')).toBeVisible();
});
📝 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('Ensure a part can be created and then deleted @hipanel-module-stock @manager', async ({ managerPage }) => {
const partView = new PartCreateView(managerPage);
await partView.navigate();
await partView.fillPartFields(partData);
await partView.save();
await expect(managerPage.locator('text=Part has been created')).toBeVisible();
await managerPage.click('text=Delete');
managerPage.on('dialog', async dialog => await dialog.accept());
await expect(managerPage.locator('text=Part has been deleted')).toBeVisible();
});
test('Ensure a part can be created and then deleted @hipanel-module-stock @manager', async ({ managerPage }) => {
const partView = new PartCreateView(managerPage);
const uniquePartData = createPartData();
await partView.navigate();
await partView.fillPartFields(uniquePartData);
await partView.save();
await expect(managerPage.locator('text=Part has been created')).toBeVisible();
// Register dialog handler before clicking delete
managerPage.on('dialog', async dialog => await dialog.accept());
await managerPage.click('text=Delete');
await expect(managerPage.locator('text=Part has been deleted')).toBeVisible();
});

Comment on lines 71 to 78
test('Ensure multiple parts can be created @hipanel-module-stock @manager', async ({ managerPage }) => {
const partView = new PartCreateView(managerPage);
await partView.navigate();
await partView.fillPartFields(partData);
await partView.addPart();
await partView.save();
await expect(managerPage.locator('text=Part has been created')).toBeVisible();
});
Copy link

Choose a reason for hiding this comment

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

🛠️ Refactor suggestion

Use dynamic test data and add validation for multiple parts

Each test should use unique data, and this test should verify that multiple parts were actually created.

test('Ensure multiple parts can be created @hipanel-module-stock @manager', async ({ managerPage }) => {
    const partView = new PartCreateView(managerPage);
+    const uniquePartData = createPartData();
    await partView.navigate();
-    await partView.fillPartFields(partData);
+    await partView.fillPartFields(uniquePartData);
    await partView.addPart();
+    // Fill the second part with different data
+    const secondPartData = createPartData();
+    await partView.fillPartFields(secondPartData);
    await partView.save();
    await expect(managerPage.locator('text=Part has been created')).toBeVisible();
+    // Verify that multiple parts were created
+    await expect(managerPage.locator('text=Created 2 parts')).toBeVisible();
});
📝 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('Ensure multiple parts can be created @hipanel-module-stock @manager', async ({ managerPage }) => {
const partView = new PartCreateView(managerPage);
await partView.navigate();
await partView.fillPartFields(partData);
await partView.addPart();
await partView.save();
await expect(managerPage.locator('text=Part has been created')).toBeVisible();
});
test('Ensure multiple parts can be created @hipanel-module-stock @manager', async ({ managerPage }) => {
const partView = new PartCreateView(managerPage);
const uniquePartData = createPartData();
await partView.navigate();
await partView.fillPartFields(uniquePartData);
await partView.addPart();
// Fill the second part with different data
const secondPartData = createPartData();
await partView.fillPartFields(secondPartData);
await partView.save();
await expect(managerPage.locator('text=Part has been created')).toBeVisible();
// Verify that multiple parts were created
await expect(managerPage.locator('text=Created 2 parts')).toBeVisible();
});

Copy link

@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: 0

♻️ Duplicate comments (4)
tests/playwright/e2e/manager/part-create.spec.ts (4)

5-14: 🛠️ Refactor suggestion

Create a function to generate unique test data.

The current implementation uses a single partData object with a timestamp that's generated once when the module loads. This can cause data collisions if tests run in quick succession.

Replace the static object with a function that generates fresh data for each test:

-const partData = {
-    partno: 'CHASSIS EPYC 7402P',
-    src_id: 'TEST-DS-01',
-    dst_id: 'TEST-DS-02',
-    serials: `MG_TEST_PART${Date.now()}`,
-    move_descr: 'MG TEST MOVE',
-    price: 200,
-    currency: '$',
-    company_id: 'Other',
-};
+function createPartData() {
+  return {
+    partno: 'CHASSIS EPYC 7402P',
+    src_id: 'TEST-DS-01',
+    dst_id: 'TEST-DS-02',
+    serials: `MG_TEST_PART${Date.now()}_${Math.floor(Math.random() * 1000)}`,
+    move_descr: 'MG TEST MOVE',
+    price: 200,
+    currency: '$',
+    company_id: 'Other',
+  };
+}

63-69: 🛠️ Refactor suggestion

Use dynamic test data for each test run.

Each test should use unique data to prevent conflicts between test runs.

test('Ensure a part can be created @hipanel-module-stock @manager', async ({ managerPage }) => {
    const partView = new PartCreateView(managerPage);
+    const uniquePartData = createPartData();
    await partView.navigate();
-    await partView.fillPartFields(partData);
+    await partView.fillPartFields(uniquePartData);
    await partView.save();
    await expect(managerPage.locator('text=Part has been created')).toBeVisible();
});

71-78: 🛠️ Refactor suggestion

Use unique data and add validation for multiple parts creation.

The test should use unique data for each part and verify that multiple parts were actually created.

test('Ensure multiple parts can be created @hipanel-module-stock @manager', async ({ managerPage }) => {
    const partView = new PartCreateView(managerPage);
+    const uniquePartData = createPartData();
    await partView.navigate();
-    await partView.fillPartFields(partData);
+    await partView.fillPartFields(uniquePartData);
    await partView.addPart();
+    // Fill the second part with different data
+    const secondPartData = createPartData();
+    await partView.fillPartFields(secondPartData, 1); // Assuming fillPartFields can accept an index
    await partView.save();
    await expect(managerPage.locator('text=Part has been created')).toBeVisible();
+    // Verify that multiple parts were created
+    await expect(managerPage.locator('text=Parts have been created successfully')).toBeVisible();
+    // Or check for a specific count message if available
+    // await expect(managerPage.locator('text=Created 2 parts')).toBeVisible();
});

80-90: ⚠️ Potential issue

Register dialog handler before triggering the dialog.

The dialog handler should be registered before any action that might trigger a dialog to ensure it catches the dialog properly.

test('Ensure a part can be created and then deleted @hipanel-module-stock @manager', async ({ managerPage }) => {
    const partView = new PartCreateView(managerPage);
+    const uniquePartData = createPartData();
    await partView.navigate();
-    await partView.fillPartFields(partData);
+    await partView.fillPartFields(uniquePartData);
    await partView.save();
    await expect(managerPage.locator('text=Part has been created')).toBeVisible();

+    // Register dialog handler before clicking delete
+    managerPage.on('dialog', async dialog => await dialog.accept());
    await managerPage.click('text=Delete');
-    managerPage.on('dialog', async dialog => await dialog.accept());
    await expect(managerPage.locator('text=Part has been deleted')).toBeVisible();
});
🧹 Nitpick comments (2)
tests/playwright/e2e/manager/part-create.spec.ts (2)

2-2: Standardize the import style.

The import style is inconsistent: one import uses curly braces and one doesn't.

-import { test } from "@hipanel-core/fixtures";
-import {expect} from "@playwright/test";
+import { test } from "@hipanel-core/fixtures";
+import { expect } from "@playwright/test";

21-22: Consider using a more maintainable approach for repeated assertions.

The test has several repetitive assertions checking the item count. Consider using a helper function to make this more maintainable.

test('Ensure part management buttons work @hipanel-module-stock @manager', async ({ managerPage }) => {
    const partView = new PartCreateView(managerPage);
    await partView.navigate();

+    // Helper function to assert item count
+    async function assertItemCount(expected: number) {
+        expect(await managerPage.locator('div.item').count()).toBe(expected);
+    }
+
    let n = await managerPage.locator('div.item').count();
-    expect(n).toBe(1);
+    await assertItemCount(1);

    await partView.addPart();
-    expect(await managerPage.locator('div.item').count()).toBe(++n);
+    await assertItemCount(++n);

    await partView.addPart();
-    expect(await managerPage.locator('div.item').count()).toBe(++n);
+    await assertItemCount(++n);

    await partView.copyPart();
-    expect(await managerPage.locator('div.item').count()).toBe(++n);
+    await assertItemCount(++n);

    await partView.removePart();
-    expect(await managerPage.locator('div.item').count()).toBe(--n);
+    await assertItemCount(--n);

    await partView.removePart();
-    expect(await managerPage.locator('div.item').count()).toBe(--n);
+    await assertItemCount(--n);

    await partView.removePart();
-    expect(await managerPage.locator('div.item').count()).toBe(--n);
+    await assertItemCount(--n);
});

Also applies to: 25-26, 28-29, 31-32, 34-35, 37-38, 40-41

📜 Review details

Configuration used: CodeRabbit UI
Review profile: CHILL
Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 70ab712 and 76baf83.

📒 Files selected for processing (2)
  • tests/playwright/e2e/manager/part-create.spec.ts (1 hunks)
  • tests/playwright/page/PartCreateView.ts (1 hunks)
🚧 Files skipped from review as they are similar to previous changes (1)
  • tests/playwright/page/PartCreateView.ts
🔇 Additional comments (3)
tests/playwright/e2e/manager/part-create.spec.ts (3)

16-41: Excellent test coverage for part management UI operations!

This test validates all the basic UI operations (add, copy, remove parts) and verifies the item count after each action, providing solid coverage of the basic functionality.


43-61: Comprehensive validation of error messages - good practice!

This test effectively verifies that all required fields show appropriate error messages when attempting to save without data. The loop approach for checking error messages is clean and maintainable.


12-12:

❓ Verification inconclusive

Verify currency format consistency across the application.

The currency is defined as '$' here, but error messages check for "Currency cannot be blank." Verify that '$' is considered a valid currency value in the application.


🏁 Script executed:

#!/bin/bash
# Check if there are any validation functions for currency in the codebase

echo "Searching for currency validation functions..."
rg -A 10 -B 10 "currency.*valid|valid.*currency" --glob "*.{php,js,ts}"

echo "Checking if '$' is used as a valid currency value elsewhere..."
rg "currency.*['\"]\\$['\"]|['\"]\\$['\"].*currency" --glob "*.{php,js,ts}"

Length of output: 434


Below is a revised verification step to help confirm whether using '$' as the currency value is properly supported across the application. It appears that the initial shell commands ran into quoting issues. Please run the following revised shell script to re-check for any currency validation functions and usages:

#!/bin/bash
# Revised script to search for currency validation functions and currency usages with proper quoting

echo "Searching for currency validation functions..."
rg -A 10 -B 10 "currency.*valid|valid.*currency" --glob "*.{php,js,ts}"

echo "Checking if '\$' is used as a valid currency value elsewhere..."
rg -A 10 -B 10 "currency.*['\"]\\\$['\"]" --glob "*.{php,js,ts}"

Action: Verify Currency Validation Consistency

  • In tests/playwright/e2e/manager/part-create.spec.ts at line 12 the code specifies currency: '$',.
  • Please confirm that using '$' meets the intended validation rules—and that it doesn’t incorrectly trigger the "Currency cannot be blank." error elsewhere in the application.

Once the revised script’s output is reviewed and validated, please ensure the currency format consistency is maintained.

Copy link

@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

🧹 Nitpick comments (7)
tests/playwright/page/PartCreateView.ts (1)

16-27: Consider creating a typed interface for part data.

The fillPartFields method accepts an any type for partData, which reduces type safety. Creating a specific interface would make the code more self-documenting and help catch errors at compile time.

+interface PartData {
+    partno: string;
+    src_id: string;
+    dst_id: string;
+    serials: string;
+    move_descr: string;
+    price: number;
+    currency: string;
+    company_id: string;
+}

 export default class PartCreateView {
     private page: Page;

     public constructor(page: Page) {
         this.page = page;
     }

     public async navigate() {
         await this.page.goto('/stock/part/create');
     }

-    public async fillPartFields(partData: any) {
+    public async fillPartFields(partData: PartData) {
         await Select2.field(this.page, `select[id$=partno]`).setValue(partData.partno);
         await Select2.field(this.page, `select[id$=src_id]`).setValue(partData.src_id);
         await Select2.field(this.page, `select[id$=dst_ids]`).setValue(partData.dst_id);

         await this.page.fill('input[id$=serials]', partData.serials);
         await this.page.fill('input[id$=move_descr]', partData.move_descr);

         await PriceWithCurrency.field(this.page, "part", 0).setSumAndCurrency(partData.price, partData.currency);

         await this.page.selectOption('select[id$=company_id]', partData.company_id);
     }
tests/playwright/e2e/manager/part/part-replace.spec.ts (1)

4-19: Consider using test.beforeEach for test data setup.

The test data is defined at the top level, which is fine for a single test but would be better organized within a test.beforeEach block if more tests are added later, especially if they need to use fresh data for each run.

 import { test } from "@hipanel-core/fixtures";
 import PartIndexView from "@hipanel-module-stock/page/PartIndexView";
 
-const data = {
-    filters: [
-        {
-            name: "move_descr_ilike",
-            value: "test description"
-        },
-        {
-            name: "model_types",
-            value: "cpu"
-        },
-    ],
-    replaceData: [
-        { serialno: `test${Date.now()}` },
-        { serialno: `test${Date.now()}` }
-    ],
-};
 
 test.describe("Part Replacement", () => {
+    let data: {
+        filters: Array<{name: string, value: string}>;
+        replaceData: Array<{serialno: string}>;
+    };
+    
+    test.beforeEach(() => {
+        data = {
+            filters: [
+                {
+                    name: "move_descr_ilike",
+                    value: "test description"
+                },
+                {
+                    name: "model_types",
+                    value: "cpu"
+                },
+            ],
+            replaceData: [
+                { serialno: `test${Date.now()}` },
+                { serialno: `test${Date.now()}` }
+            ],
+        };
+    });
+
     test("Ensure parts can be replaced @hipanel-module-stock @manager", async ({ managerPage }) => {
tests/playwright/page/PartIndexView.ts (1)

31-35: Consider making the fillReplaceForm method more robust.

The current implementation uses a complex CSS selector that might be brittle if the table structure changes. Consider using data attributes or more reliable selectors if possible.

 public async fillReplaceForm(replaceData: { serialno: string }[], rowNumbers: number[]) {
     for (const [index, data] of replaceData.entries()) {
-        await this.page.fill(`table tbody tr:nth-child(${rowNumbers[index]}) input[name='serialno']`, data.serialno);
+        // Assuming rows have a data attribute or can be identified more reliably
+        await this.page.fill(`table tbody tr[data-key="${rowNumbers[index]}"] input[name='serialno']`, data.serialno);
+        // Or use locator for more flexibility
+        const row = this.page.locator(`table tbody tr`).nth(rowNumbers[index] - 1);
+        await row.locator(`input[name='serialno']`).fill(data.serialno);
     }
 }

If the table structure is fixed and well-defined, you might want to add a comment documenting the selector structure for future maintainers.

tests/playwright/e2e/manager/part/part-create.spec.ts (4)

1-14: Good test data setup with dynamic values for uniqueness.

Using a timestamp in the serial number is a good approach to prevent test data collisions. The test data is well-structured with all necessary fields for part creation.

Consider extracting test data to a separate fixture or helper function to allow reuse across different test files:

- const partData = {
-     partno: 'CHASSIS EPYC 7402P',
-     src_id: 'TEST-DS-01',
-     dst_id: 'TEST-DS-02',
-     serials: `MG_TEST_PART${Date.now()}`,
-     move_descr: 'MG TEST MOVE',
-     price: 200,
-     currency: '$',
-     company_id: 'Other',
- };
+ import { generatePartData } from '@hipanel-module-stock/fixtures/part-data';
+ 
+ const partData = generatePartData();

16-41: Testing UI interactions and element counts is a good approach.

This test effectively verifies that the part management buttons work correctly by checking the count of parts after each operation.

Consider adding descriptive comments about the expected behavior for better maintainability:

 test('Ensure part management buttons work @hipanel-module-stock @manager', async ({ managerPage }) => {
     const partView = new PartCreateView(managerPage);
     await partView.navigate();

     let n = await managerPage.locator('div.item').count();
     expect(n).toBe(1);

+    // Adding parts should increase the count
     await partView.addPart();
     expect(await managerPage.locator('div.item').count()).toBe(++n);

     await partView.addPart();
     expect(await managerPage.locator('div.item').count()).toBe(++n);

+    // Copying a part should also increase the count
     await partView.copyPart();
     expect(await managerPage.locator('div.item').count()).toBe(++n);

+    // Removing parts should decrease the count
     await partView.removePart();
     expect(await managerPage.locator('div.item').count()).toBe(--n);

     await partView.removePart();
     expect(await managerPage.locator('div.item').count()).toBe(--n);

     await partView.removePart();
     expect(await managerPage.locator('div.item').count()).toBe(--n);
 });

63-69: Simple and effective test for part creation.

The test confirms that a part can be successfully created with the provided data.

Consider extending the test to verify more than just the success message:

 test('Ensure a part can be created @hipanel-module-stock @manager', async ({ managerPage }) => {
     const partView = new PartCreateView(managerPage);
     await partView.navigate();
     await partView.fillPartFields(partData);
     await partView.save();
     await expect(managerPage.locator('text=Part has been created')).toBeVisible();
+    
+    // Verify we're redirected to the index page
+    await expect(managerPage).toHaveURL(/index/);
+    
+    // Verify the created part appears in the list
+    await expect(managerPage.locator(`text=${partData.partno}`)).toBeVisible();
+    await expect(managerPage.locator(`text=${partData.serials}`)).toBeVisible();
 });

1-91: Consider adding tests for edge cases and error scenarios.

The test suite covers the basic functionality well but lacks tests for edge cases and error scenarios.

Consider adding tests for:

  1. Creating a part with an existing serial number (expecting an error)
  2. Creating a part with invalid price values
  3. Testing sorting and filtering in the parts list
  4. Testing the cancel button functionality

Example test for duplicate serial:

test('Should show error when creating part with existing serial @hipanel-module-stock @manager', async ({ managerPage }) => {
    const partView = new PartCreateView(managerPage);
    
    // Create first part
    await partView.navigate();
    const testData = { ...partData, serials: `DUPLICATE_SERIAL_${Date.now()}` };
    await partView.fillPartFields(testData);
    await partView.save();
    await expect(managerPage.locator('text=Part has been created')).toBeVisible();
    
    // Try to create second part with same serial
    await partView.navigate();
    await partView.fillPartFields(testData);
    await partView.save();
    await expect(managerPage.locator('text=Serial number has already been taken')).toBeVisible();
});
📜 Review details

Configuration used: CodeRabbit UI
Review profile: CHILL
Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 76baf83 and 275f019.

📒 Files selected for processing (5)
  • tests/playwright/e2e/manager/part/part-create.spec.ts (1 hunks)
  • tests/playwright/e2e/manager/part/part-replace.spec.ts (1 hunks)
  • tests/playwright/input/PriceWithCurrency.ts (1 hunks)
  • tests/playwright/page/PartCreateView.ts (1 hunks)
  • tests/playwright/page/PartIndexView.ts (1 hunks)
🔇 Additional comments (4)
tests/playwright/input/PriceWithCurrency.ts (1)

1-8: Class implementation looks clean and focused.

The PriceWithCurrency class provides a nice abstraction for price input fields with currency support. The static factory method makes it easy to create instances with the correct identifier pattern.

tests/playwright/page/PartCreateView.ts (2)

24-24: Use string interpolation safety for PriceWithCurrency.

Similar to the previous review comment on currency selectors, ensure that string values are properly escaped when passed to methods to prevent potential injection issues.

Let's check the implementation of the setSumAndCurrency method to ensure it uses proper string escaping:

#!/bin/bash
# Check the implementation of setSumAndCurrency in InputWithCurrency
fd -t file "InputWithCurrency.(ts|js)" | xargs cat | grep -A 15 "setSumAndCurrency"

3-3:

❓ Verification inconclusive

Verify import path consistency.

The import path @hipanel-module-stock/input/PriceWithCurrency seems inconsistent with the file location we're reviewing (tests/playwright/input/PriceWithCurrency.ts). Please ensure this import resolves correctly in your project setup.


🏁 Script executed:

#!/bin/bash
# Check if there's another PriceWithCurrency implementation in the codebase
fd -t file "PriceWithCurrency.(ts|js)" --exclude "tests/playwright/input/PriceWithCurrency.ts"

# Check if there's a path mapping in tsconfig.json for @hipanel-module-stock
fd -t file "tsconfig.json" | xargs cat | grep -A 10 "paths"

Length of output: 152


Confirm Import Alias Configuration

The import statement

import PriceWithCurrency from "@hipanel-module-stock/input/PriceWithCurrency";

references an alias that appears inconsistent with the actual file location at tests/playwright/input/PriceWithCurrency.ts. Our initial automated checks (search for alternative implementations and tsconfig path mappings) did not yield evidence of an alias configuration that would resolve @hipanel-module-stock to the tests/playwright/input directory.

Please verify the following manually:

  • Confirm that the alias @hipanel-module-stock is correctly configured (e.g., in tsconfig.json, Webpack, or another bundler configuration) to point to the intended directory.
  • Ensure that this import resolves as expected in your project’s setup across different environments (development, testing, production).
  • If the alias mapping is not present or is misconfigured, either update the configuration or adjust the import path to match the file structure.
tests/playwright/e2e/manager/part/part-create.spec.ts (1)

43-61:

❓ Verification inconclusive

Good validation testing with comprehensive error message checks.

This test effectively validates that form submission is blocked when required fields are missing and that appropriate error messages are displayed.

Ensure that these error messages are consistent with the actual application. If translations are used, the messages might change:


🏁 Script executed:

#!/bin/bash
# Check for error messages in the project's language files
grep -r "Part No. cannot be blank" --include="*.php" --include="*.json" .
grep -r "Source cannot be blank" --include="*.php" --include="*.json" .
grep -r "Destination cannot be blank" --include="*.php" --include="*.json" .
# Add more relevant searches as needed

Length of output: 228


Action Required: Manual Verification of Error Message Translations

The test case effectively ensures that form submission is blocked when required fields are missing and checks for multiple error messages. However, our automated search did not locate these messages in the language files. Please manually verify that the error messages (e.g., "Part No. cannot be blank.", "Source cannot be blank.", etc.) match the actual text displayed by the application—especially if translations or dynamic localization is used.

  • Review Points:
    • Ensure that the messages tested in the spec are consistent with those defined in your language/translation files.
    • Confirm that any translation updates or alternate language usage has been reflected in both the tests and the application's UI.

Comment on lines 27 to 29
await partIndexView.selectPartsToReplace(1, data.replaceData.length);
// await partIndexView.fillReplaceForm(data.replaceData, data.rowNumbers);
//await partIndexView.confirmReplacement();
Copy link

Choose a reason for hiding this comment

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

🛠️ Refactor suggestion

Remove debug pause and uncomment implementation.

There's a pause() call in the test, which is typically used for debugging and should be removed before merging. Additionally, there are commented-out lines suggesting the test implementation is incomplete.

 await partIndexView.navigate();
 await partIndexView.applyFilters(data.filters);
 await partIndexView.selectPartsToReplace(1, data.replaceData.length);
-await partIndexView.pause();
-// await partIndexView.fillReplaceForm(data.replaceData, data.rowNumbers);
-//await partIndexView.confirmReplacement();
+await partIndexView.fillReplaceForm(data.replaceData, [1, 2]); // Assuming row numbers correspond to the selected range
+await partIndexView.confirmReplacement();

Committable suggestion skipped: line range outside the PR's diff.

}

public async confirmReplacement() {
await this.index.clickBulkButton('Peplace');
Copy link

Choose a reason for hiding this comment

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

⚠️ Potential issue

Fix typo in button label.

There's a typo in the button label: 'Peplace' instead of 'Replace'. This could cause the test to fail if it's looking for the correct button.

 public async confirmReplacement() {
-    await this.index.clickBulkButton('Peplace');
+    await this.index.clickBulkButton('Replace');
     await this.index.hasNotification('Parts have been replaced');
 }
📝 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.index.clickBulkButton('Peplace');
public async confirmReplacement() {
- await this.index.clickBulkButton('Peplace');
+ await this.index.clickBulkButton('Replace');
await this.index.hasNotification('Parts have been replaced');
}


public async selectPartsToReplace(start: number, end: number) {
await this.index.chooseRangeOfRowsOnTable(start, end);
await this.page.pause();
Copy link

Choose a reason for hiding this comment

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

🛠️ Refactor suggestion

Remove debug pause from selectPartsToReplace method.

The pause() method is typically used for debugging and should be removed before merging to production code.

 public async selectPartsToReplace(start: number, end: number) {
     await this.index.chooseRangeOfRowsOnTable(start, end);
-    await this.page.pause();
     await this.index.clickBulkButton('Replace');
 }
📝 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.pause();
public async selectPartsToReplace(start: number, end: number) {
await this.index.chooseRangeOfRowsOnTable(start, end);
await this.index.clickBulkButton('Replace');
}

Comment on lines 80 to 90
test('Ensure a part can be created and then deleted @hipanel-module-stock @manager', async ({ managerPage }) => {
const partView = new PartCreateView(managerPage);
await partView.navigate();
await partView.fillPartFields(partData);
await partView.save();
await expect(managerPage.locator('text=Part has been created')).toBeVisible();

await managerPage.click('text=Delete');
managerPage.on('dialog', async dialog => await dialog.accept());
await expect(managerPage.locator('text=Part has been deleted')).toBeVisible();
});
Copy link

Choose a reason for hiding this comment

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

⚠️ Potential issue

The dialog handling setup should be before clicking the Delete button.

The current implementation sets up the dialog handler after clicking the delete button, which might lead to race conditions where the dialog appears before the handler is set up.

Move the dialog handler setup before clicking the delete button:

 test('Ensure a part can be created and then deleted @hipanel-module-stock @manager', async ({ managerPage }) => {
     const partView = new PartCreateView(managerPage);
     await partView.navigate();
     await partView.fillPartFields(partData);
     await partView.save();
     await expect(managerPage.locator('text=Part has been created')).toBeVisible();

+    managerPage.on('dialog', async dialog => await dialog.accept());
     await managerPage.click('text=Delete');
-    managerPage.on('dialog', async dialog => await dialog.accept());
     await expect(managerPage.locator('text=Part has been deleted')).toBeVisible();
 });
📝 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('Ensure a part can be created and then deleted @hipanel-module-stock @manager', async ({ managerPage }) => {
const partView = new PartCreateView(managerPage);
await partView.navigate();
await partView.fillPartFields(partData);
await partView.save();
await expect(managerPage.locator('text=Part has been created')).toBeVisible();
await managerPage.click('text=Delete');
managerPage.on('dialog', async dialog => await dialog.accept());
await expect(managerPage.locator('text=Part has been deleted')).toBeVisible();
});
test('Ensure a part can be created and then deleted @hipanel-module-stock @manager', async ({ managerPage }) => {
const partView = new PartCreateView(managerPage);
await partView.navigate();
await partView.fillPartFields(partData);
await partView.save();
await expect(managerPage.locator('text=Part has been created')).toBeVisible();
managerPage.on('dialog', async dialog => await dialog.accept());
await managerPage.click('text=Delete');
await expect(managerPage.locator('text=Part has been deleted')).toBeVisible();
});

Comment on lines 71 to 78
test('Ensure multiple parts can be created @hipanel-module-stock @manager', async ({ managerPage }) => {
const partView = new PartCreateView(managerPage);
await partView.navigate();
await partView.fillPartFields(partData);
await partView.addPart();
await partView.save();
await expect(managerPage.locator('text=Part has been created')).toBeVisible();
});
Copy link

Choose a reason for hiding this comment

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

🛠️ Refactor suggestion

Multiple parts creation test needs verification of both parts.

The test adds a second part form but doesn't verify that both parts are actually created.

Enhance the test to verify that both parts are created correctly:

 test('Ensure multiple parts can be created @hipanel-module-stock @manager', async ({ managerPage }) => {
     const partView = new PartCreateView(managerPage);
     await partView.navigate();
+    
+    // Create unique serial numbers for each part to verify later
+    const serial1 = `MG_TEST_PART1_${Date.now()}`;
+    const serial2 = `MG_TEST_PART2_${Date.now()}`;
+    
+    // Fill the first part
+    const firstPartData = { ...partData, serials: serial1 };
+    await partView.fillPartFields(firstPartData);
-    await partView.fillPartFields(partData);
     await partView.addPart();
+    
+    // Fill the second part
+    const secondPartData = { ...partData, serials: serial2 };
+    await partView.fillPartFields(secondPartData, 1); // Assuming second form is at index 1
+    
     await partView.save();
     await expect(managerPage.locator('text=Part has been created')).toBeVisible();
+    
+    // Verify both parts appear in the list
+    await expect(managerPage.locator(`text=${serial1}`)).toBeVisible();
+    await expect(managerPage.locator(`text=${serial2}`)).toBeVisible();
 });
📝 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('Ensure multiple parts can be created @hipanel-module-stock @manager', async ({ managerPage }) => {
const partView = new PartCreateView(managerPage);
await partView.navigate();
await partView.fillPartFields(partData);
await partView.addPart();
await partView.save();
await expect(managerPage.locator('text=Part has been created')).toBeVisible();
});
test('Ensure multiple parts can be created @hipanel-module-stock @manager', async ({ managerPage }) => {
const partView = new PartCreateView(managerPage);
await partView.navigate();
// Create unique serial numbers for each part to verify later
const serial1 = `MG_TEST_PART1_${Date.now()}`;
const serial2 = `MG_TEST_PART2_${Date.now()}`;
// Fill the first part
const firstPartData = { ...partData, serials: serial1 };
await partView.fillPartFields(firstPartData);
await partView.addPart();
// Fill the second part
const secondPartData = { ...partData, serials: serial2 };
await partView.fillPartFields(secondPartData, 1); // Assuming second form is at index 1
await partView.save();
await expect(managerPage.locator('text=Part has been created')).toBeVisible();
// Verify both parts appear in the list
await expect(managerPage.locator(`text=${serial1}`)).toBeVisible();
await expect(managerPage.locator(`text=${serial2}`)).toBeVisible();
});

Copy link

@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: 1

♻️ Duplicate comments (2)
tests/playwright/e2e/manager/part/part-create.spec.ts (2)

72-80: 🛠️ Refactor suggestion

Enhance multiple parts creation test to verify both parts individually.

The current test doesn't verify that both parts are actually created with the expected data.

 test('Ensure multiple parts can be created @hipanel-module-stock @manager', async ({ managerPage }) => {
     const partView = new PartCreateView(managerPage);
     await partView.navigate();
-    await partView.fillPartFields(partData);
+    
+    // Create unique serial numbers for each part
+    const serial1 = UniqueId.generate(`MG_TEST_PART1`);
+    const serial2 = UniqueId.generate(`MG_TEST_PART2`);
+    
+    // Fill the first part
+    const firstPartData = createPartData({ serials: serial1 });
+    await partView.fillPartFields(firstPartData);
     await partView.addPart();
-    await partView.fillPartFields(partData, 1);
+    
+    // Fill the second part with different data
+    const secondPartData = createPartData({ serials: serial2 });
+    await partView.fillPartFields(secondPartData, 1);
+    
     await partView.save();
     await expect(managerPage.locator('text=Part has been created')).toBeVisible();
+    
+    // Navigate to part list view to verify both parts exist
+    await managerPage.click('text=Parts');
+    await expect(managerPage.locator(`text=${serial1}`)).toBeVisible();
+    await expect(managerPage.locator(`text=${serial2}`)).toBeVisible();
 });

89-90: ⚠️ Potential issue

Fix the race condition in dialog handling.

The dialog handler is set up AFTER clicking the delete button, which could lead to a race condition where the dialog appears before the handler is set up, causing the test to hang.

 await expect(managerPage.locator('text=Part has been created')).toBeVisible();

+managerPage.on('dialog', async dialog => await dialog.accept());
 await managerPage.click('text=Delete');
-managerPage.on('dialog', async dialog => await dialog.accept());
 await expect(managerPage.locator('text=Part has been deleted')).toBeVisible();
🧹 Nitpick comments (10)
tests/playwright/helper/PartForm.ts (3)

12-23: Consider using a typed interface instead of any for partData.

Using the any type bypasses TypeScript's type checking benefits. Consider defining a specific interface for the expected data structure.

-public async fillPartFields(partData: any, index: number = 0) {
+interface PartData {
+    partno: string;
+    src_id: string;
+    dst_id: string;
+    serials: string;
+    move_descr: string;
+    price: number | string;
+    currency: string;
+    company_id: string;
+}
+
+public async fillPartFields(partData: PartData, index: number = 0) {

29-31: Clarify the fillSerials method signature.

The method name suggests it accepts multiple serials, but it takes a single string parameter. Consider renaming or modifying the method to accept an array.

-public async fillSerials(serial: string, index: number = 0) {
+public async fillSerial(serial: string, index: number = 0) {
    await this.page.fill(this.selector('input', 'serials', index), serial);
}

+public async fillSerials(serials: string[], index: number = 0) {
+    await this.page.fill(this.selector('input', 'serials', index), serials.join('\n'));
+}

40-42: Consider using a more specific selector for the save button.

The current text-based selector could be fragile if button text changes or if multiple buttons with the same text exist. Consider using a more specific selector.

public async save() {
-    await this.page.click('button:has-text("Save")');
+    await this.page.click('button.btn-success:has-text("Save"), button[type="submit"]:has-text("Save")');
}
tests/playwright/page/PartCreateView.ts (3)

13-15: Consider making the path configurable or using a relative URL.

Hardcoded paths might break if the application is deployed to a different base URL. Consider making it configurable or using a relative URL.

public async navigate() {
-    await this.page.goto('/stock/part/create');
+    await this.page.goto('./stock/part/create');
}

17-19: Consider propagating a typed interface instead of any.

This method inherits the any type issue from the PartForm class. Consider using a typed interface for better type safety.


25-35: Consider more robust selectors for UI interactions.

The current selectors rely on specific HTML structure, particularly the "last-child" selector. This could be fragile if the HTML structure changes. Consider using more specific selectors or data attributes.

public async addPart() {
-    await this.page.click("div.item:last-child button.add-item");
+    await this.page.click("div.item:not(.hidden):last-child button.add-item");
}

public async removePart() {
-    await this.page.click("div.item:last-child button.remove-item");
+    await this.page.click("div.item:not(.hidden):last-child button.remove-item");
}

public async copyPart() {
-    await this.page.click("div.item:last-child button.copy");
+    await this.page.click("div.item:not(.hidden):last-child button.copy");
}
tests/playwright/e2e/manager/part/part-replace.spec.ts (1)

6-21: Consider moving test data to a separate factory or fixture.

Extracting test data to a separate fixture or factory would improve maintainability and allow for easier reuse across tests.

+import { createPartReplaceTestData } from '@hipanel-module-stock/fixtures/partReplaceFixtures';

-const data = {
-    filters: [
-        {
-            name: "move_descr_ilike",
-            value: "test description"
-        },
-        {
-            name: "model_types",
-            value: "cpu"
-        },
-    ],
-    replaceData: [
-        { serialno: UniqueId.generate(`test`) },
-        { serialno: UniqueId.generate(`test`) }
-    ],
-};
+const data = createPartReplaceTestData();
tests/playwright/e2e/manager/part/part-create.spec.ts (3)

6-15: Consider creating a test data factory for better test isolation.

The current approach using a shared partData object could lead to test interference if multiple tests run in parallel or if tests modify the data object. A factory function would provide fresh data for each test.

-const partData = {
-    partno: 'CHASSIS EPYC 7402P',
-    src_id: 'TEST-DS-01',
-    dst_id: 'TEST-DS-02',
-    serials: UniqueId.generate(`MG_TEST_PART`),
-    move_descr: 'MG TEST MOVE',
-    price: 200,
-    currency: '$',
-    company_id: 'Other',
-};

+function createPartData(customData = {}) {
+    return {
+        partno: 'CHASSIS EPYC 7402P',
+        src_id: 'TEST-DS-01',
+        dst_id: 'TEST-DS-02',
+        serials: UniqueId.generate(`MG_TEST_PART`),
+        move_descr: 'MG TEST MOVE',
+        price: 200,
+        currency: '$',
+        company_id: 'Other',
+        ...customData
+    };
+}

64-70: Consider enhancing verification of part creation.

This test confirms the success message appears, but doesn't verify the part was created with the correct data. Add navigation to part details or list view to confirm the part exists with expected properties.

 test('Ensure a part can be created @hipanel-module-stock @manager', async ({ managerPage }) => {
     const partView = new PartCreateView(managerPage);
     await partView.navigate();
-    await partView.fillPartFields(partData);
+    const testData = createPartData();
+    await partView.fillPartFields(testData);
     await partView.save();
     await expect(managerPage.locator('text=Part has been created')).toBeVisible();
+    
+    // Navigate to part list view to verify the part exists
+    await managerPage.click('text=Parts');
+    await expect(managerPage.locator(`text=${testData.serials}`)).toBeVisible();
+    await expect(managerPage.locator(`text=${testData.partno}`)).toBeVisible();
 });

82-92: Consider adding cleanup for test data.

This is the only test that cleans up after itself by deleting the created part. Consider adding similar cleanup steps to other tests to prevent test data accumulation.

For other tests, you could add an afterEach hook that navigates to the parts list and deletes any parts created during tests:

test.afterEach(async ({ managerPage }) => {
    // Navigate to parts list
    await managerPage.click('text=Parts');
    
    // Look for parts with the test prefix in serials
    const testParts = await managerPage.locator('//tr[contains(., "MG_TEST_PART")]');
    const count = await testParts.count();
    
    if (count > 0) {
        // Select all test parts
        for (let i = 0; i < count; i++) {
            await testParts.nth(i).locator('input[type="checkbox"]').check();
        }
        
        // Delete selected parts
        await managerPage.click('text=Delete');
        managerPage.on('dialog', async dialog => await dialog.accept());
        await expect(managerPage.locator('text=Part has been deleted')).toBeVisible();
    }
});
📜 Review details

Configuration used: CodeRabbit UI
Review profile: CHILL
Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 275f019 and 054d9cf.

📒 Files selected for processing (6)
  • tests/playwright/e2e/manager/part/part-create.spec.ts (1 hunks)
  • tests/playwright/e2e/manager/part/part-replace.spec.ts (1 hunks)
  • tests/playwright/helper/PartForm.ts (1 hunks)
  • tests/playwright/page/PartCreateView.ts (1 hunks)
  • tests/playwright/page/PartIndexView.ts (1 hunks)
  • tests/playwright/page/PartReplaceView.ts (1 hunks)
🚧 Files skipped from review as they are similar to previous changes (1)
  • tests/playwright/page/PartIndexView.ts
🔇 Additional comments (9)
tests/playwright/page/PartReplaceView.ts (3)

1-11: Clean class initialization and dependency management.

The code properly imports the required dependencies and initializes the class with a Page object, following a good pattern of dependency injection and encapsulation.


13-19: Well-structured form filling logic with proper iteration.

The method uses a clean approach to iterate through replace data and increment keys. This is a good pattern for handling form fields with dynamic indices.


21-23: Good delegation pattern for form submission.

The class appropriately delegates the save functionality to the underlying PartForm component, promoting code reuse and maintainability.

tests/playwright/helper/PartForm.ts (2)

25-27: Well-defined reusable selector pattern.

Good approach for creating consistent selectors based on element type, name, and index. This promotes maintainability and readability.


33-38: Good implementation with clear documentation.

The comment clearly explains the inconsistency in field naming between different pages, which is valuable information for maintainers.

tests/playwright/page/PartCreateView.ts (2)

1-11: Clean class initialization and dependency management.

The class properly imports dependencies and initializes with a Page object, following good patterns of dependency injection.


21-23: Good delegation pattern for form submission.

The class appropriately delegates the save functionality to the underlying PartForm component, promoting code reuse and maintainability.

tests/playwright/e2e/manager/part/part-create.spec.ts (2)

18-42: LGTM! Effective testing of part management interface.

The test thoroughly verifies UI controls for part management by checking the count of items after each operation, ensuring proper interaction with the form elements.


44-62: LGTM! Well-structured validation test.

Good approach using a list of expected error messages and verifying each one. This makes the test maintainable as error messages can be easily updated.

Comment on lines 23 to 36
test.describe("Part Replacement", () => {
test("Ensure parts can be replaced @hipanel-module-stock @manager", async ({ managerPage }) => {
const partIndexView = new PartIndexView(managerPage);
const partReplaceView = new PartReplaceView(managerPage);

await partIndexView.navigate();
await partIndexView.applyFilters(data.filters);
await partIndexView.selectPartsToReplace(1, data.replaceData.length);

await partReplaceView.fillReplaceForm(data.replaceData);
await partReplaceView.save();

await partIndexView.confirmReplacement();
});
Copy link

Choose a reason for hiding this comment

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

🛠️ Refactor suggestion

Add assertions to verify the replacement succeeded.

The test confirms that the form can be submitted, but doesn't verify that the replacement was actually successful. Consider adding assertions to check the results.

await partReplaceView.save();

await partIndexView.confirmReplacement();
+
+// Add assertions to verify successful replacement
+await partIndexView.navigate();
+await partIndexView.applyFilters([
+    {
+        name: "serial_like",
+        value: data.replaceData[0].serialno
+    }
+]);
+await partIndexView.expectToSeePartWithSerial(data.replaceData[0].serialno);
📝 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.describe("Part Replacement", () => {
test("Ensure parts can be replaced @hipanel-module-stock @manager", async ({ managerPage }) => {
const partIndexView = new PartIndexView(managerPage);
const partReplaceView = new PartReplaceView(managerPage);
await partIndexView.navigate();
await partIndexView.applyFilters(data.filters);
await partIndexView.selectPartsToReplace(1, data.replaceData.length);
await partReplaceView.fillReplaceForm(data.replaceData);
await partReplaceView.save();
await partIndexView.confirmReplacement();
});
test.describe("Part Replacement", () => {
test("Ensure parts can be replaced @hipanel-module-stock @manager", async ({ managerPage }) => {
const partIndexView = new PartIndexView(managerPage);
const partReplaceView = new PartReplaceView(managerPage);
await partIndexView.navigate();
await partIndexView.applyFilters(data.filters);
await partIndexView.selectPartsToReplace(1, data.replaceData.length);
await partReplaceView.fillReplaceForm(data.replaceData);
await partReplaceView.save();
await partIndexView.confirmReplacement();
// Add assertions to verify successful replacement
await partIndexView.navigate();
await partIndexView.applyFilters([
{
name: "serial_like",
value: data.replaceData[0].serialno
}
]);
await partIndexView.expectToSeePartWithSerial(data.replaceData[0].serialno);
});
});

Copy link

@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: 2

🧹 Nitpick comments (3)
tests/playwright/page/PartIndexView.ts (3)

13-15: Consider using a configuration-based URL approach.

The hardcoded URL path could be challenging to maintain if the application structure changes. Consider extracting this to a configuration variable or a constants file.

 async navigate() {
-    await this.page.goto("/stock/part/index?representation=common");
+    await this.page.goto(URLS.PART_INDEX_PAGE);
 }

Where URLS would be imported from a constants file containing all application URLs.


43-45: Consider making this method private or enhancing its functionality.

This method is a simple passthrough to the index object's method with the same name. Consider either making it private if it's only used internally, or enhancing it with additional functionality if it needs to be public.


47-60: Add flexibility to seePartWasCreated method.

The method currently uses a hardcoded row number (1) which assumes the created part will always be the first row. This might be brittle if sorting changes or multiple parts are created.

-public async seePartWasCreated() {
-    const rowNumber = 1;
+public async seePartWasCreated(rowNumber = 1) {
     await this.index.hasNotification('Part has been created');
     await this.index.closeNotification();

     // Ensure the current URL matches expected Move index URL
     await expect(this.page).toHaveURL(/\/stock\/move\/index\?MoveSearch%5Bid%5D=/);

     // Get first row move ID from the index table
     const moveId = await this.index.getRowDataKeyByNumber(rowNumber);
     expect(moveId).not.toBeNull();

     await this.index.clickColumnOnTable('Parts', rowNumber);
 }
📜 Review details

Configuration used: CodeRabbit UI
Review profile: CHILL
Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 054d9cf and 7d1325d.

📒 Files selected for processing (2)
  • tests/playwright/e2e/manager/part/part-create.spec.ts (1 hunks)
  • tests/playwright/page/PartIndexView.ts (1 hunks)
🚧 Files skipped from review as they are similar to previous changes (1)
  • tests/playwright/e2e/manager/part/part-create.spec.ts
🔇 Additional comments (3)
tests/playwright/page/PartIndexView.ts (3)

1-11: Good implementation of the page object pattern.

The class is well-structured with proper private properties and a constructor that accepts a Playwright Page object. This follows best practices for Page Object Model design.


17-23: Well-structured filter application method.

This method elegantly handles applying multiple filters and submitting the search form.


25-28: Using dropdown bulk button is the correct approach.

The method correctly implements selecting parts and triggering the replace action through the dropdown bulk button. The previous issue with the debug pause has been addressed.

Comment on lines +34 to +41
public async deleteItemOnTable(number: number) {
await this.chooseNumberRowOnTable(number);

await this.page.getByRole('button', { name: 'Delete' }).click();

this.page.on('dialog', async dialog => await dialog.accept());
await this.index.hasNotification('Part has been deleted');
}
Copy link

Choose a reason for hiding this comment

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

⚠️ Potential issue

Fix event handler registration timing in deleteItemOnTable method.

The dialog event handler is registered after clicking the delete button, which could lead to race conditions. The event handler should be registered before triggering the action.

 public async deleteItemOnTable(number: number) {
     await this.chooseNumberRowOnTable(number);
 
+    this.page.on('dialog', async dialog => await dialog.accept());
     await this.page.getByRole('button', { name: 'Delete' }).click();
-
-    this.page.on('dialog', async dialog => await dialog.accept());
     await this.index.hasNotification('Part has been deleted');
 }
📝 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
public async deleteItemOnTable(number: number) {
await this.chooseNumberRowOnTable(number);
await this.page.getByRole('button', { name: 'Delete' }).click();
this.page.on('dialog', async dialog => await dialog.accept());
await this.index.hasNotification('Part has been deleted');
}
public async deleteItemOnTable(number: number) {
await this.chooseNumberRowOnTable(number);
this.page.on('dialog', async dialog => await dialog.accept());
await this.page.getByRole('button', { name: 'Delete' }).click();
await this.index.hasNotification('Part has been deleted');
}

Comment on lines +30 to +32
public async confirmReplacement() {
await this.index.hasNotification('Part has been replaced');
}
Copy link

Choose a reason for hiding this comment

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

💡 Verification agent

🧩 Analysis chain

Consider handling single vs. multiple part replacements.

The notification message only checks for a singular form ("Part has been replaced"). If multiple parts are replaced, the message might use a plural form.


🏁 Script executed:

#!/bin/bash
# Description: Check if there are different notification messages for single vs multiple part replacements

# Look for notification strings in the codebase
echo "Searching for part replacement notification strings in the codebase..."
rg -i "part(s?)( have| has) been replaced" --type php --type js --type ts

Length of output: 642


Action Required: Update Notification Handling for Multiple Part Replacements

After searching the codebase, it’s confirmed that all part replacement notifications currently use the singular message ("Part has been replaced")—across controllers, messages, and tests. If the application supports replacing multiple parts at once, please consider implementing conditional logic (or pluralized messages) in both the notification generation and corresponding tests. If single replacements are the only supported scenario, adding a clarifying comment regarding this design decision would be beneficial.

Copy link

@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: 1

♻️ Duplicate comments (1)
tests/playwright/page/PartIndexView.ts (1)

34-41: ⚠️ Potential issue

Fix event handler registration timing in deleteItemOnTable method

The dialog event handler is registered after clicking the delete button, which leads to race conditions. The event handler should be registered before triggering the action.

public async deleteItemOnTable(number: number) {
    await this.chooseNumberRowOnTable(number);

+   this.page.on('dialog', async dialog => await dialog.accept());
    await this.page.getByRole('button', { name: 'Delete' }).click();
-
-   this.page.on('dialog', async dialog => await dialog.accept());
    await this.index.hasNotification('Part has been deleted');
}
🧹 Nitpick comments (7)
tests/playwright/page/PartIndexView.ts (4)

17-23: Consider adding error handling for filter application

The method handles filter application, but doesn't include any error handling if a filter fails to apply or if the search submission fails.

async applyFilters(filters: Array<{ name: string; value: string }>) {
    try {
        for (const filter of filters) {
            await this.index.setFilter(filter.name, filter.value);
        }

        await this.index.submitSearchButton();
+       // Optional: wait for results to load
+       await this.page.waitForLoadState('networkidle');
+   } catch (error) {
+       console.error(`Failed to apply filters: ${error}`);
+       throw new Error(`Failed to apply filters: ${error}`);
+   }
}

43-45: Consider making the wrapper method private or adding functionality

The chooseNumberRowOnTable method is just a wrapper around the same method in the index class. Consider either:

  1. Making it private if it's only used internally
  2. Adding additional functionality to justify it being a public method
  3. Removing it and directly using index.chooseNumberRowOnTable where needed

47-61: Improve the seePartWasCreated method with better comments and assertions

This method combines several actions (checking notifications, URL validation, extracting IDs, and navigation). Consider breaking it down and improving comments to better explain the workflow.

public async seePartWasCreated() {
    const rowNumber = 1;
    await this.index.hasNotification('Part has been created');
    await this.index.closeNotification();

-   // Ensure the current URL matches expected Move index URL
+   // After part creation, we're redirected to Move index page with the created move ID
    await expect(this.page).toHaveURL(/\/stock\/move\/index\?MoveSearch%5Bid%5D=/);

-   // Get first row move ID from the index table
+   // Extract the move ID from the first row to verify it exists
    const moveId = await this.index.getRowDataKeyByNumber(rowNumber);
    expect(moveId).not.toBeNull();

-   // Wait /stock/part/view page to load
+   // Navigate to the Part details page by clicking the Parts column in the first row
    await this.index.clickColumnOnTable('Parts', rowNumber);
+   // Wait for the part view page to load completely
+   await this.page.waitForLoadState('networkidle');
}

1-62: Add consistent access modifiers and method return types

Some methods have explicit 'public' modifiers while others don't. Some methods like navigate() and applyFilters() should be made consistently public or private based on their usage patterns.

-async navigate() {
+public async navigate(): Promise<void> {
    await this.page.goto("/stock/part/index?representation=common");
}

-async applyFilters(filters: Array<{ name: string; value: string }>) {
+public async applyFilters(filters: Array<{ name: string; value: string }>): Promise<void> {
    for (const filter of filters) {
        await this.index.setFilter(filter.name, filter.value);
    }

    await this.index.submitSearchButton();
}
tests/playwright/page/PartView.ts (3)

17-20: Consider more flexible dialog handling.

Currently, the dialog handler automatically accepts all dialogs. This might not be the desired behavior in all cases, especially for confirmation dialogs where users might want to cancel in some test scenarios.

Consider implementing a more flexible approach:

- private registerAcceptDeleteDialogHandler() {
-     // By default, dialogs are auto-dismissed by Playwright, so you don't have to handle them
-     this.page.on('dialog', async dialog => await dialog.accept());
- }
+ private registerDialogHandler(shouldAccept: boolean = true) {
+     // Configure dialog handling behavior
+     this.page.on('dialog', async dialog => {
+         shouldAccept ? await dialog.accept() : await dialog.dismiss();
+     });
+ }

Then update the constructor to use this method:

 public constructor(page: Page) {
     this.page = page;
     this.notification = new Notification(page);
     this.detailMenu = new DetailMenu(page);
-    this.registerAcceptDeleteDialogHandler();
+    this.registerDialogHandler(true); // Default to accepting dialogs
 }

22-26: Add error handling and consistent formatting.

The deletePart method lacks error handling and has inconsistent formatting with the opening brace on a new line (unlike other methods).

- public async deletePart()
- {
-     await this.detailMenu.clickDetailMenuItem("Delete");
-     await this.notification.hasNotification('Part has been deleted');
- }
+ public async deletePart(): Promise<boolean> {
+     try {
+         await this.detailMenu.clickDetailMenuItem("Delete");
+         await this.notification.hasNotification('Part has been deleted');
+         return true;
+     } catch (error) {
+         console.error('Failed to delete part:', error);
+         return false;
+     }
+ }

This change adds:

  1. Consistent brace positioning
  2. Error handling with try/catch
  3. Return value to indicate success/failure
  4. Return type annotation for better type safety

1-27: Add JSDoc comments for better documentation.

Consider adding JSDoc comments to improve code documentation and maintainability.

 import {Page} from "@playwright/test";
 import Notification from "@hipanel-core/helper/Notification";
 import DetailMenu from "@hipanel-core/helper/DetailMenu";
 
+/**
+ * Page object representing the Part view page.
+ * Provides methods to interact with a part's detail view.
+ */
 export default class PartView {
     private page: Page;
     private notification: Notification;
     private detailMenu: DetailMenu;
 
+    /**
+     * Creates a new PartView instance.
+     * @param page - The Playwright Page object
+     */
     public constructor(page: Page) {
         this.page = page;
         this.notification = new Notification(page);
         this.detailMenu = new DetailMenu(page);
         this.registerAcceptDeleteDialogHandler();
     }
 
+    /**
+     * Registers a handler to automatically accept all dialogs.
+     * @private
+     */
     private registerAcceptDeleteDialogHandler() {
         // By default, dialogs are auto-dismissed by Playwright, so you don't have to handle them
         this.page.on('dialog', async dialog => await dialog.accept());
     }
 
+    /**
+     * Deletes the currently displayed part.
+     * @returns Promise that resolves when the part is deleted and notification appears
+     */
     public async deletePart()
     {
         await this.detailMenu.clickDetailMenuItem("Delete");
         await this.notification.hasNotification('Part has been deleted');
     }
 }
📜 Review details

Configuration used: CodeRabbit UI
Review profile: CHILL
Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 7d1325d and 0a54c35.

📒 Files selected for processing (4)
  • src/views/part/view.php (1 hunks)
  • tests/playwright/e2e/manager/part/part-create.spec.ts (1 hunks)
  • tests/playwright/page/PartIndexView.ts (1 hunks)
  • tests/playwright/page/PartView.ts (1 hunks)
✅ Files skipped from review due to trivial changes (1)
  • src/views/part/view.php
🚧 Files skipped from review as they are similar to previous changes (1)
  • tests/playwright/e2e/manager/part/part-create.spec.ts
🔇 Additional comments (4)
tests/playwright/page/PartIndexView.ts (3)

1-15: Good implementation of the Page Object Model

The class is well-structured and follows the Page Object Model design pattern, effectively encapsulating page interactions for the parts index view. The constructor properly initializes dependencies, and the navigate method includes a specific representation parameter.


25-28: LGTM on selectPartsToReplace implementation

The method correctly uses the index helper to select a range of rows and then clicks the appropriate bulk action button. Previous debug pause has been removed as suggested.


30-32: Update notification check to handle multiple part replacements

The notification message only checks for a singular form ("Part has been replaced"). If multiple parts are replaced, the message might use a plural form.

#!/bin/bash
# Search for plural notification messages for part replacements in the application
echo "Searching for part replacement notification strings..."
rg -i "parts? (has|have) been replaced" --type php --type ts
tests/playwright/page/PartView.ts (1)

5-16: LGTM! Well-structured class implementation.

The class structure is clean with proper encapsulation and clear property initialization in the constructor.

Comment on lines +1 to +4
import {Page} from "@playwright/test";
import Notification from "@hipanel-core/helper/Notification";
import DetailMenu from "@hipanel-core/helper/DetailMenu";

Copy link

Choose a reason for hiding this comment

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

💡 Verification agent

🧩 Analysis chain

Verify imports consistency with other page objects.

Ensure that the import style is consistent with other page objects in the codebase.


🏁 Script executed:

#!/bin/bash
# Check imports in other page objects for consistency

echo "Checking import styles in other page object files:"
fd -e ts -p "tests/playwright/page/.*View\.ts" --exec head -n 5 | grep -v "PartView.ts"

Length of output: 876


Action: Unify Import Formatting Across Page Objects

The file tests/playwright/page/PartView.ts currently uses the following imports:

import {Page} from "@playwright/test";
import Notification from "@hipanel-core/helper/Notification";
import DetailMenu from "@hipanel-core/helper/DetailMenu";

A quick survey of similar page object files shows variations in style:

  • PartIndexView.ts:
    import {expect, Page} from "@playwright/test";
    import Index from "@hipanel-core/page/Index";
  • PartReplaceView.ts:
    import { Page } from "@playwright/test";
    import PartForm from "@hipanel-module-stock/helper/PartForm";
  • PartCreateView.ts:
    import {Page}from "@playwright/test";
    import PartForm from "@hipanel-module-stock/helper/PartForm";

While the imports in PartView.ts are functionally correct, there is inconsistency in spacing (inside curly braces and before the from keyword) across the page objects. Please update these statements to a unified, project-wide format to maintain consistency.

Copy link

@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: 0

♻️ Duplicate comments (1)
tests/playwright/page/PartIndexView.ts (1)

42-49: ⚠️ Potential issue

Fix event handler registration timing in deleteItemOnTable method.

The dialog event handler is registered after clicking the delete button, which could lead to race conditions. The event handler should be registered before triggering the action.

    public async deleteItemOnTable(number: number) {
        await this.chooseNumberRowOnTable(number);

+        this.page.on('dialog', async dialog => await dialog.accept());
        await this.page.getByRole('button', { name: 'Delete' }).click();

-        this.page.on('dialog', async dialog => await dialog.accept());
        await this.index.hasNotification('Part has been deleted');
    }
🧹 Nitpick comments (11)
tests/playwright/page/PartIndexView.ts (8)

13-15: Consider adding a return type to the navigate method.

While TypeScript can infer the return type, it's good practice to explicitly define return types for public methods to improve code clarity and maintainability.

-    public async navigate() {
+    public async navigate(): Promise<void> {
        await this.page.goto("/stock/part/index?representation=common");
    }

17-23: Consider adding explicit return type and improving error handling.

Adding return types enhances code clarity, and error handling would make this method more robust.

-    public async applyFilters(filters: Array<{ name: string; value: string }>) {
+    public async applyFilters(filters: Array<{ name: string; value: string }>): Promise<void> {
        for (const filter of filters) {
+            try {
                await this.index.setFilter(filter.name, filter.value);
+            } catch (error) {
+                console.error(`Failed to set filter ${filter.name}:`, error);
+                throw error;
+            }
        }

        await this.index.submitSearchButton();
    }

25-27: Add explicit return type to filterBySerial method.

For consistency and better type safety.

-    public async filterBySerial(serial: string) {
+    public async filterBySerial(serial: string): Promise<void> {
        await this.index.applyFilter('serial_ilike', serial);
    }

29-32: Update method parameter description and add explicit return type.

The method name 'selectPartsToReplace' suggests selecting specific parts, but the implementation selects a range from 1 to count. Consider more descriptive parameter naming or documentation.

-    public async selectPartsToReplace(count: number) {
+    public async selectPartsToReplace(count: number): Promise<void> {
+        // Selects the first 'count' rows in the table for replacement
        await this.selectRows(count);
        await this.index.clickDropdownBulkButton('Bulk actions', 'Replace');
    }

34-36: Add explicit return type to selectRows method.

For consistency with other methods.

-    public async selectRows(count: number) {
+    public async selectRows(count: number): Promise<void> {
        await this.index.chooseRangeOfRowsOnTable(1, count);
    }

51-53: Add explicit return type to chooseNumberRowOnTable method.

For consistency and better type safety.

-    public async chooseNumberRowOnTable(number: number) {
+    public async chooseNumberRowOnTable(number: number): Promise<void> {
        await this.index.chooseNumberRowOnTable(number);
    }

55-69: Consider decomposing seePartWasCreated into smaller methods and add return type.

This method performs multiple actions and could be broken down for better maintainability. Also, adding return types improves type safety.

-    public async seePartWasCreated() {
+    public async seePartWasCreated(): Promise<void> {
        const rowNumber = 1;
        await this.index.hasNotification('Part has been created');
        await this.index.closeNotification();

        // Ensure the current URL matches expected Move index URL
        await expect(this.page).toHaveURL(/\/stock\/move\/index\?MoveSearch%5Bid%5D=/);

        // Get first row move ID from the index table
        const moveId = await this.index.getRowDataKeyByNumber(rowNumber);
        expect(moveId).not.toBeNull();

        // Wait /stock/part/view page to load
        await this.index.clickColumnOnTable('Parts', rowNumber);
    }

Consider refactoring this into smaller methods like:

  1. verifyPartCreationNotification()
  2. verifyMoveIndexUrl()
  3. verifyMoveIdAndNavigateToPartView(rowNumber)

71-73: Add explicit return type to openSellModal method.

For consistency and better type safety.

-    public async openSellModal() {
+    public async openSellModal(): Promise<void> {
        await this.index.clickDropdownBulkButton('Sell parts', 'Sell parts');
    }
tests/playwright/page/BillIndexView.ts (3)

13-15: Add explicit return type to navigate method.

For consistency and better type safety.

-    public async navigate() {
+    public async navigate(): Promise<void> {
        await this.page.goto('/finance/bill/index');
    }

17-28: Add explicit return type and consider adding error handling and timeout.

For a more robust implementation, consider adding error handling and possibly a timeout for the page loading.

-    public async ensureSellingBillWasCreated(sellData: { descr: string; prices: number[] }) {
+    public async ensureSellingBillWasCreated(sellData: { descr: string; prices: number[] }): Promise<void> {
+        try {
            await this.navigate();

            // Apply filter for the description
            await this.index.applyFilter('descr', sellData.descr);

            // Open the row menu of the first bill and select "View"
            await this.index.clickPopoverMenu(1, 'View');

            // Ensure the correct number of parts were sold
            await expect(this.page.locator('tr table tr[data-key]')).toHaveCount(sellData.prices.length);
+        } catch (error) {
+            console.error('Failed to verify selling bill creation:', error);
+            throw error;
+        }
    }

17-28: Consider adding a more descriptive comment for the billing verification.

The current comment "Ensure the correct number of parts were sold" could be enhanced to better explain the assertion logic.

    public async ensureSellingBillWasCreated(sellData: { descr: string; prices: number[] }) {
        await this.navigate();

        // Apply filter for the description
        await this.index.applyFilter('descr', sellData.descr);

        // Open the row menu of the first bill and select "View"
        await this.index.clickPopoverMenu(1, 'View');

-        // Ensure the correct number of parts were sold
+        // Verify that the number of rows in the bill details table matches the number of prices in the sell data,
+        // confirming that all parts were properly included in the bill
        await expect(this.page.locator('tr table tr[data-key]')).toHaveCount(sellData.prices.length);
    }
📜 Review details

Configuration used: CodeRabbit UI
Review profile: CHILL
Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between bdf19bc and d82437a.

📒 Files selected for processing (4)
  • tests/playwright/e2e/manager/part/part-sell.spec.ts (1 hunks)
  • tests/playwright/page/BillIndexView.ts (1 hunks)
  • tests/playwright/page/PartIndexView.ts (1 hunks)
  • tests/playwright/page/SellModal.ts (1 hunks)
🚧 Files skipped from review as they are similar to previous changes (2)
  • tests/playwright/e2e/manager/part/part-sell.spec.ts
  • tests/playwright/page/SellModal.ts
🔇 Additional comments (3)
tests/playwright/page/PartIndexView.ts (2)

4-11: Class structure looks good!

The class is well-structured with proper private fields and a clean constructor. Good practice using TypeScript's access modifiers.


38-40: Update notification message to handle plural form.

The notification message only checks for a singular form ("Part has been replaced"). If multiple parts can be replaced, consider handling both singular and plural forms.

#!/bin/bash
# Check for different notification messages for part replacements

echo "Searching for part replacement notification message in the codebase..."
rg -i "part(s?)( have| has) been replaced" --type php --type ts --type js
tests/playwright/page/BillIndexView.ts (1)

4-11: Class structure looks good!

The class is well-structured with proper private fields and a clean constructor. Good practice using TypeScript's access modifiers.

Copy link

@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: 1

🧹 Nitpick comments (2)
tests/playwright/page/SellModal.ts (2)

34-36: Consider adding a wait for navigation or state change after confirming the sale

After clicking the sell button, the page likely changes state or navigates. It's a good practice to wait for this state change to avoid potential race conditions in tests.

public async confirmSale() {
    await this.modalLocator().locator(`button:text('Sell')`).click();
+   // Wait for the modal to close or for navigation/state change
+   await this.page.waitForSelector("div.modal-body[data-action-url$=sell]", { state: "hidden" });
}

1-41: Add JSDoc comments to improve code documentation

Adding JSDoc comments to methods will improve code documentation and provide better IDE hints for users of this class.

Here's an example for the fillSellWindowFields method:

+ /**
+  * Fills in the sell modal form with the provided data
+  * @param sellData - The data to fill in the form
+  * @param sellData.contact_id - The contact ID to select
+  * @param sellData.time - The time value to input
+  * @param sellData.descr - The description text
+  * @param sellData.currency - The currency option to select
+  * @param sellData.prices - Array of price values to input in respective fields
+  */
public async fillSellWindowFields(sellData: SellData) {
📜 Review details

Configuration used: CodeRabbit UI
Review profile: CHILL
Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between d82437a and 3202572.

📒 Files selected for processing (1)
  • tests/playwright/page/SellModal.ts (1 hunks)
🔇 Additional comments (5)
tests/playwright/page/SellModal.ts (5)

1-4: Appropriate imports for the Playwright page object

The imports look good, properly bringing in Playwright components and custom helper classes.


5-12: Well-structured class with appropriate constructor initialization

The class follows good OOP principles with proper encapsulation and initialization.


30-32: Good encapsulation of the modal selector

Extracting the modal selector into a private method is a good practice for maintainability.


38-40: Good validation of successful operation

Checking for a success notification is a good way to verify the operation completed successfully.


1-41:

✅ Verification successful

Verify integration with the test that uses this class

Since this is a new page object, it would be good to verify that it's used correctly in the test and that all the selectors work as expected.


🏁 Script executed:

#!/bin/bash
# Find and examine the tests that use SellModal
echo "Searching for tests that import SellModal..."
rg -l "import.*SellModal" --type ts tests/playwright

# Look for the part-sell.spec.ts file mentioned in the AI summary
echo "Looking for part-sell.spec.ts..."
if [ -f "tests/playwright/e2e/manager/part/part-sell.spec.ts" ]; then
  echo "Found part-sell.spec.ts"
  cat "tests/playwright/e2e/manager/part/part-sell.spec.ts"
else
  echo "part-sell.spec.ts not found"
fi

Length of output: 1710


Integration Verified: SellModal is Correctly Utilized in Tests

The integration with the test (specifically in tests/playwright/e2e/manager/part/part-sell.spec.ts) has been verified. The SellModal class is being imported and used as expected, and all selectors (such as those in fillSellWindowFields and modalLocator) align correctly with the test's workflow.

No changes are required.

Comment on lines 14 to 28
public async fillSellWindowFields(sellData) {
const modal = this.modalLocator();

await Select2.field(this.page, 'select[id$=contact_id]').setValue(sellData.contact_id);

await modal.locator("input[name*=time]").fill(sellData.time);
await modal.locator("textarea[id$=description]").fill(sellData.descr);
await modal.locator("select[id$=currency]").selectOption(sellData.currency);

const priceFields = await modal.locator("div[class$=sell] input[type=text][id^=partsell]")
.all();
for (let i = 0; i < sellData.prices.length; i++) {
await priceFields[i].fill(sellData.prices[i].toString());
}
}
Copy link

Choose a reason for hiding this comment

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

🛠️ Refactor suggestion

Add TypeScript interface for the sellData parameter

The sellData parameter lacks type information, which reduces type safety and makes it harder for other developers to understand what's expected.

+ interface SellData {
+   contact_id: string;
+   time: string;
+   descr: string;
+   currency: string;
+   prices: number[];
+ }

- public async fillSellWindowFields(sellData) {
+ public async fillSellWindowFields(sellData: SellData) {

Additionally, consider adding error handling to check if there are enough price fields for the provided prices array:

  const priceFields = await modal.locator("div[class$=sell] input[type=text][id^=partsell]")
    .all();
+ if (priceFields.length < sellData.prices.length) {
+   throw new Error(`Expected at least ${sellData.prices.length} price fields but found ${priceFields.length}`);
+ }
  for (let i = 0; i < sellData.prices.length; i++) {
    await priceFields[i].fill(sellData.prices[i].toString());
  }
📝 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
public async fillSellWindowFields(sellData) {
const modal = this.modalLocator();
await Select2.field(this.page, 'select[id$=contact_id]').setValue(sellData.contact_id);
await modal.locator("input[name*=time]").fill(sellData.time);
await modal.locator("textarea[id$=description]").fill(sellData.descr);
await modal.locator("select[id$=currency]").selectOption(sellData.currency);
const priceFields = await modal.locator("div[class$=sell] input[type=text][id^=partsell]")
.all();
for (let i = 0; i < sellData.prices.length; i++) {
await priceFields[i].fill(sellData.prices[i].toString());
}
}
interface SellData {
contact_id: string;
time: string;
descr: string;
currency: string;
prices: number[];
}
public async fillSellWindowFields(sellData: SellData) {
const modal = this.modalLocator();
await Select2.field(this.page, 'select[id$=contact_id]').setValue(sellData.contact_id);
await modal.locator("input[name*=time]").fill(sellData.time);
await modal.locator("textarea[id$=description]").fill(sellData.descr);
await modal.locator("select[id$=currency]").selectOption(sellData.currency);
const priceFields = await modal.locator("div[class$=sell] input[type=text][id^=partsell]")
.all();
if (priceFields.length < sellData.prices.length) {
throw new Error(`Expected at least ${sellData.prices.length} price fields but found ${priceFields.length}`);
}
for (let i = 0; i < sellData.prices.length; i++) {
await priceFields[i].fill(sellData.prices[i].toString());
}
}

@SilverFire SilverFire merged commit 68b9f93 into hiqdev:master Mar 7, 2025
1 check passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

2 participants