diff --git a/src/bcbench/agent/shared/config.yaml b/src/bcbench/agent/shared/config.yaml index b5dd27f64..95c552be0 100644 --- a/src/bcbench/agent/shared/config.yaml +++ b/src/bcbench/agent/shared/config.yaml @@ -51,7 +51,7 @@ prompt: {% endif %} code-review-template: | - /review + Use the `al-code-review` skill to review the current working-tree AL file changes. Run a full-domain review (do not pass a domain, so all domains run). Review only the uncommitted working-tree changes (git diff HEAD); do not compare commits such as HEAD~1..HEAD or origin/main. @@ -75,7 +75,7 @@ prompt: # NOTE: the canonical source file is AGENTS.md; it is automatically renamed # to the agent-specific filename (AgentType.instruction_filename) during setup instructions: - enabled: false + enabled: true # controls: # 1. whether to copy skills from `src/bcbench/agent/shared/instructions//skills/` diff --git a/src/bcbench/agent/shared/instructions/microsoft-BCApps/agents/ALTest.agent.md b/src/bcbench/agent/shared/instructions/microsoft-BCApps/agents/ALTest.agent.md deleted file mode 100644 index 1c24f0de9..000000000 --- a/src/bcbench/agent/shared/instructions/microsoft-BCApps/agents/ALTest.agent.md +++ /dev/null @@ -1,267 +0,0 @@ ---- -name: ALTest -description: Instructions for creating AL tests. ---- - - -You are an AL test automation engineer for Microsoft Dynamics 365 Business Central. - - - -Your task is to implement automated tests in the AL language for Microsoft Dynamics 365 Business Central (test codeunits and related test artifacts). Focus on producing runnable, deterministic AL tests that validate Business Central application behavior. - - -**CRITICAL: PRESERVE ALL EXISTING CODE** -- NEVER remove, delete, or simplify existing test code - it was generated by another agent and verified by a human developer. -- NEVER add new setup code, business logic, or "improvements" beyond what's in the rules below. -- Your job is ONLY to fix formatting, structure, and coding standard violations - NOT to change test logic. -- If code seems unnecessary or wrong, LEAVE IT - the human developer approved it. - -**CRITICAL: BUILD MUST SUCCEED** -- Your output must compile in the target test project. -- Avoid introducing new objects (new codeunits/files) unless absolutely required (see Build Robustness rules). - - -### Test Structure - -**Required format:** -```al -[Test] -procedure DescriptiveProcedureName() -begin - // [FEATURE] [AI test] - // [SCENARIO 123456] Brief one-line description - Initialize(); - - // [GIVEN] Setup preconditions - LibrarySales.CreateCustomer(Customer); - LibrarySales.CreateSalesInvoice(SalesInvoice, Customer); - - // [GIVEN] More setup preconditions - LibraryPurchase.CreateVendor(Vendor); - LibraryPurchase.CreatePurchaseInvoice(PurchaseInvoice, Vendor); - - // [WHEN] Execute the action - Customer.Validate(Name, 'Test'); - - // [THEN] Verify expected outcome - Assert.AreEqual('Test', Customer.Name, 'Name should be updated'); -end; -``` - -**Rules:** -- `// [FEATURE] [AI test]` must be first line after `begin` -- `// [SCENARIO ]` on next line - work item ID is REQUIRED: `// [SCENARIO 123456] Description` -- `Initialize();` immediately after [SCENARIO] -- Each [GIVEN]/[WHEN]/[THEN] comments must be preceded by an empty line -- Interleave [GIVEN]/[WHEN]/[THEN] comments with code -- In COMMENTS, refer to entities with 1-2 letters: "C", "V", "C1" (e.g., `// [GIVEN] Customer "C" with Sales Invoice "SI"`) -- Variable names must be FULL names, not abbreviated: `CustomerNo`, `VendorNo`, `ItemNo` (NOT `C`, `V`, `CustNo`, `VendNo`) -- Use rounded amounts without decimals - - - -### Build Robustness (NEW — MUST FOLLOW) - -**Primary rule: prefer edits over new objects** -1) Prefer adding a new `[Test]` procedure to an EXISTING test codeunit in the same app/test project. -2) Avoid creating new test codeunits/files unless: - - no suitable existing test codeunit exists, AND - - the project’s object ID ranges and dependencies are known and satisfied. - -**Object identifiers** -- If a new object is unavoidable: - - Object name must be <= 30 characters (AL object identifier constraint). - - Object ID must be within the allowed ranges for that project. - - Object ID must be unused (search before choosing). - - If ranges are unknown, do NOT create a new object; instead, add the test to an existing codeunit. - -**Dependencies (critical)** -- Do NOT reference codeunits/libraries that are not available in the target test project. -- Specifically: `Library - Variable Storage` is OPTIONAL and must only be used if it exists in the project. - -**Symbol correctness** -- Do NOT call procedures/fields that do not exist in the target branch/project. -- If you use a helper procedure (e.g., `SomeRec.SomeHelper()`), it must exist in the codebase. - -**Build preflight checklist (must pass mentally before finalizing)** -- No new codeunit IDs unless absolutely required -- No object name > 30 chars -- No duplicate object IDs -- No “missing codeunit/library” references -- No invented procedures/fields - - - -### Test Library Usage Requirements - -1. **Global Variable Declaration** - - All library variables MUST be declared in the global var section. - - Do NOT pass libraries as function parameters. - -2. **Required Libraries** -| Library | Purpose | -|---------|---------| -| Assert | Assertions | -| Library XPath XML Reader | Read and verify XML content | -| Library Sales | Sales related operations (customer, sales invoice) | -| Library Purchase | Purchase related operations (vendor, purchase invoice) | -| Library ERM | General ERM functionality (general journal, G/L account) | -| Library Utility | Random test data, number series, generic record operations | -| Library Random | Random numbers, decimals, dates, text strings | -| Library Inventory | Items, unit of measures, inventory-related setup and posting | -| Library Dimension | Dimensions and dimension values | -| Library Journals | General journal lines, batches, templates | -| Library Marketing | Contacts and marketing-related entities | -| Library Fixed Asset | Fixed asset related operations | -| Library Warehouse | Locations, bins, zones, warehouse documents and operations | -| Library Manufacturing | Production orders, BOMs, routings, work centers | -| Library File Mgt Handler | Intercepting and handling file download operations | -| Library ERM Country Data | Country-specific setup data initialization | -| Library Notification Mgt | Recalling, disabling, managing notifications | -| Library Text File Validation | Reading, searching, validating values in text files | -| Library Lower Permissions | Setting, adding, managing permission sets | - -3. **Library - Variable Storage** - - Use to pass data between test and handler procedures. - - If used, MUST add `LibraryVariableStorage.AssertEmpty()` at the end of test. - -4. **Library - Setup Storage** - - Use in Initialize procedure if any setup table is modified in tests. - - - -### Coding Standard Requirements - -1. **FORBIDDEN Patterns** - - ❌ Conditional statements (if/else) in test body - - ❌ DotNet variables - - ❌ Interface invocations - use implementation codeunits instead - - ❌ Verification in handler procedures - - ❌ Commit calls in helper or handler procedures (only in test body) - - ❌ Modifying working date (unless absolutely necessary) - - ❌ TestField for assertions - use Assert.AreEqual instead - - ❌ **DELETING OR REMOVING CODE** - NEVER delete, remove, or simplify any test code. All code was verified by a human developer. - - ❌ **ADDING NEW LOGIC** - NEVER add new setup code, filters, validations, or business logic. Only fix structure/formatting issues. - -2. **REQUIRED Patterns** - - ✅ After `asserterror` in [WHEN], add both `Assert.ExpectedError()` AND `Assert.ExpectedErrorCode()` in [THEN] - - ✅ Multiple verifications should use a local `Verify*` procedure - - ✅ Reuse existing local procedures when possible - - ✅ Handler procedures should only set values, not verify - -3. **Amount Handling** - - Do NOT assign or redefine amounts in test body if already defined in helper functions. - - Trust helper function's default value and omit amount assignment. - - If amount should be verified, create new local variable and assign from helper function return. - -4. **Codeunit Procedure Order** - MUST be enforced, move procedures if needed: - 1. Test procedures (with [Test] attribute) - MUST come FIRST - 2. Initialize procedure - 3. Local helper procedures (use `Verify` prefix for verification procedures) - 4. Handler procedures (at the end of codeunit) - - If tests are placed after Initialize(), MOVE them before Initialize(). - -5. **Handler Procedures** - - Use [HandlerFunctions] attribute on test procedure. - - Only set values, never verify in handlers. - - - -### Common Issues and Fixes - -1. **Missing Initialize()** - ```al - // BEFORE (wrong): - begin - // [FEATURE] [AI test] - // [SCENARIO] Test something - // [GIVEN] Some setup - - // AFTER (correct): - begin - // [FEATURE] [AI test] - // [SCENARIO] Test something - Initialize(); - - // [GIVEN] Some setup - ``` - -2. **Inline Record Creation → Library Usage** - ```al - // BEFORE (wrong): - Customer.Init(); - Customer."No." := 'CUST001'; - Customer.Insert(); - - // AFTER (correct): - LibrarySales.CreateCustomer(Customer); - ``` - -3. **Conditional in Test → Separate Tests** - ```al - // BEFORE (wrong): - if Condition then - Assert.IsTrue(Result1, 'Msg1') - else - Assert.IsTrue(Result2, 'Msg2'); - - // AFTER: Create two separate test procedures - ``` - -4. **Missing AssertEmpty** - ```al - // BEFORE (wrong): - LibraryVariableStorage.Enqueue(Value); - // ... test code ... - // test ends without AssertEmpty - - // AFTER (correct): - LibraryVariableStorage.Enqueue(Value); - // ... test code ... - LibraryVariableStorage.AssertEmpty(); - ``` - -5. **Missing ExpectedErrorCode** - ```al - // BEFORE (wrong): - // [WHEN] - asserterror SomeOperation(); - // [THEN] - Assert.ExpectedError('Error message'); - - // AFTER (correct): - // [WHEN] - asserterror SomeOperation(); - // [THEN] - Assert.ExpectedError('Error message'); - Assert.ExpectedErrorCode('Dialog'); - ``` - -6. **Verification in Handler** - ```al - // BEFORE (wrong): - [MessageHandler] - procedure MessageHandler(Message: Text[1024]) - begin - Assert.AreEqual('Expected', Message, 'Wrong message'); - end; - - // AFTER (correct): - [MessageHandler] - procedure MessageHandler(Message: Text[1024]) - begin - LibraryVariableStorage.Enqueue(Message); - end; - // Then verify in test body after the action - ``` - -7. **TestField → Assert.AreEqual** - ```al - // BEFORE (wrong): - GenJnlLine.TestField("IRS 1099 Reporting Period", NewPeriodNo); - - // AFTER (correct): - Assert.AreEqual(NewPeriodNo, GenJnlLine."IRS 1099 Reporting Period", 'Reporting period is incorrect'); - ``` - diff --git a/src/bcbench/agent/shared/instructions/microsoft-BCApps/skills/al-code-review/SKILL.md b/src/bcbench/agent/shared/instructions/microsoft-BCApps/skills/al-code-review/SKILL.md new file mode 100644 index 000000000..e7efeeb86 --- /dev/null +++ b/src/bcbench/agent/shared/instructions/microsoft-BCApps/skills/al-code-review/SKILL.md @@ -0,0 +1,275 @@ +--- +name: al-code-review +description: 'Review AL code for Dynamics 365 Business Central across specialized domains. Supports domain-specific analysis (security, performance, style, accessibility, upgrade, privacy) with expertise-matched models. Handles Microsoft Dynamics 365 localization architecture (W1 base + country-specific layers).' +allowed-tools: Read, Glob, Grep, LSP +argument-hint: 'review domain: security, performance, style, accessibility, upgrade, or privacy (or leave empty to run all domains sequentially)' +--- + +# AL Code Review + +Reviews AL code for Dynamics 365 Business Central across multiple specialized domains with architectural awareness and localization strategy support. + +## When to Use + +- Reviewing AL code changes or pull requests +- Analyzing Business Central customizations for quality and best practices +- Evaluating code for specific domain concerns (security, performance, style, accessibility, upgrade, privacy) +- User asks for "code review", "review this AL code", or domain-specific analysis + +## Domain-Specific Review Options + +The skill supports 6 specialized review domains. Each domain uses a dedicated expertise model: + +| Domain | Focus Area | Model Profile | Key Expertise | +|--------|-----------|-------------|---------------| +| **Security** | Permission models, credential management, access control, injection vulnerabilities | Security Auditor | Permissions, secrets, injection, access control, external services | +| **Performance** | Database queries, N+1 problems, indexes, record access patterns | Database Performance Specialist | Queries, indexes, SIFT, temporary tables, transactions | +| **Style** | Naming conventions, code formatting, readability, consistency | Code Style Linter | Naming, formatting, readability, AL conventions | +| **Accessibility** | Assistive technology support, labels, keyboard flow, semantic UI patterns | Accessibility Specialist | Screen readers, keyboard navigation, captions, control add-ins | +| **Upgrade** | Upgrade codeunit structure, data migration, upgrade tags, DataTransfer usage | Upgrade Code Specialist | Upgrade tags, DataTransfer, error handling, data migration | +| **Privacy** | GDPR compliance, data classification, PII handling, telemetry | Privacy/GDPR Expert | Data classification, PII, telemetry, compliance | + +## Domain Strategy + +**If you specify a domain** (for example, `security`): +- Load only that domain's instruction file +- Review only from that domain's perspective +- Return findings for that domain only + +**If you do not specify a domain**: +- Keep domain separation +- Run the review in a loop, one domain at a time: `security`, `performance`, `style`, `accessibility`, `upgrade`, `privacy` +- Load one instruction file per pass +- Aggregate the final findings, grouped by domain + +## Review Philosophy + +### Root Cause Focus (Regardless of Domain) + +- **Symptoms are clues, not conclusions** - A bug in one file often indicates a pattern problem +- **Ask "why" five times** - Dig until you find the actual cause +- **Broad strokes over band-aids** - Recommend fixes that solve classes of problems +- **Architecture over implementation** - Focus on structural issues, not just line-by-line fixes + +### Domain Instruction Mapping + +Instruction files used by domain: +- `security` → `../../instructions/security.md` +- `performance` → `../../instructions/performance.md` +- `style` → `../../instructions/style.md` +- `accessibility` → `../../instructions/accessibility.md` +- `upgrade` → `../../instructions/upgrade.md` +- `privacy` → `../../instructions/privacy.md` + +Path note: +- The evaluator copies repository instruction assets into the target repository's `.github/` folder. +- This skill is copied to `.github/skills/al-code-review/SKILL.md`. +- Domain instruction files are copied to `.github/instructions/.md`. +- From this skill location, use `../../instructions/.md`. + +## Review Process + +### 1. Input Analysis +- Determine whether to run a single-domain review or the full sequential domain loop +- Identify changed files and localization status (W1 vs. country layer) +- Determine if this is W1 auto-sync or local-only code + +### 2. File Filtering +- Apply the localization rules defined in the Localization Architecture section below +- Review W1 files as the source of truth when paired with generated country copies +- Review country-specific files directly when they contain local-only changes + +### 3. Domain-Specific Analysis +- Apply the active domain's expertise model and instruction file for the current pass +- Identify root causes for systemic issues, not just symptoms +- Evaluate severity within domain context (Critical, High, Medium, Low) +- Merge findings across passes only after all requested domain reviews are complete + +### 4. Severity & Recommendation Criteria + +| Severity | Criteria | Examples | +|----------|----------|----------| +| **Critical** | Blocking production issues, security breaches, data loss risks | Hardcoded credentials, SQL injection, unhandled exceptions in upgrade | +| **High** | Significant problems affecting functionality or performance | FindSet misuse causing N+1, missing DataClassification on PII fields | +| **Medium** | Patterns that will cause problems as code grows or during maintenance | Inconsistent naming, missing tooltips on 10+ fields, incomplete error handling | +| **Low** | Improvements enhancing code quality, readability, or maintainability | Spacing violations, unnecessary parentheses, minor documentation gaps | + +## Localization Architecture + +This localization strategy applies only to files under `Src/Layers`. It does not apply to `Src/Apps`. + +### Repository Structure + +The codebase uses Microsoft's localization layering pattern in `Src/Layers`: + +``` +Src/ +├── Layers/ +│ └── W1/ ← Worldwide base layer (source of truth) +│ ├── SystemApplication/ +│ ├── Business Foundation/ +│ │ ├── app.json +│ │ ├── App/ ← W1 source code +│ │ └── Test/ +│ └── [other modules] +│ ├── AT/ ← Austria country layer (localization) +│ ├── BE/ ← Belgium country layer +│ ├── CH/ ← Switzerland country layer +│ └── [other countries]/ +``` + +### How Localization Works + +1. **W1 is the source of truth** - All core code lives in `Src/Layers/W1` +2. **Country layers override W1** - When a customer runs in Austria (AT), the AT version of each file is used instead of W1 +3. **Automated sync** - When W1 files change, an automated script copies those changes to country layer files +4. **Local customizations** - Country layers can have local-only changes not in W1 + +### Review Rules for Localized Code in `Src/Layers` + +#### Rule 1: W1 File Changed + Country Files Changed for Same File +✅ **Review the W1 version only** +❌ **Do NOT comment on country-specific files** + +**Reason**: Country files are auto-generated copies. Any fix applied to W1 is automatically propagated to all country versions. Commenting on country files is redundant. + +**Example**: +``` +Changed files in PR: +- Src/Layers/W1/Business Foundation/App/MyCodeunit.al (modified) +- Src/Layers/AT/Business Foundation/App/MyCodeunit.al (modified) +- Src/Layers/BE/Business Foundation/App/MyCodeunit.al (modified) + +Review approach: +→ Review MyCodeunit.al in W1 only +→ If the same code change is in the local file in AT and BE, ignore it +``` + +#### Rule 2: Only Country Files Changed (No W1 Change) +✅ **Comment on the specific country file(s)** + +**Reason**: These are local-only modifications not controlled by W1 sync script. Issues are specific to that country. + +**Example**: +``` +Changed files in PR: +- Src/Layers/CH/Business Foundation/App/TaxCalc.al (modified) + +Review approach: +→ Review TaxCalc.al in CH +``` + +### How to Identify Source vs. Generated Files + +**W1 source files contain** current business logic, latest features, commented explanations + +**Country layer copies contain** identical code; if line 42 has a specific issue in W1, that same issue appears in the country copy. This indicates it's auto-generated. + +**How to Tell**: +- Open W1 version and country version side-by-side +- If they're identical or differ only in spacing, it's an auto-generated copy → review W1 only +- If country version has unique logic not in W1, it's country-specific → review both + +## Output Format + +All review findings must be returned as a JSON array, regardless of domain. The response must contain ONLY the JSON findings array inside a single `json` code block (no prose outside the code block). + +### JSON Structure + +```json +[ + { + "filePath": "src/MyCodeunit.al", + "lineNumber": 42, + "severity": "High|Medium|Low|Critical", + "issue": "Brief description of the issue", + "recommendation": "Specific recommendation to fix", + "suggestedCode": "The corrected code that fixes the issue" + } +] +``` + +### Output Rules + +- Respond with ONLY the JSON findings array +- Do NOT include explanations, commentary, or extra text outside the JSON code block +- Wrap JSON in one code block that starts with ```json +- If no findings, output: `[]` +- Each finding must have all 5 fields: filePath, lineNumber, severity, issue, recommendation, suggestedCode +- suggestedCode must be the exact line(s) to replace (with proper indentation preserved) +- If exact code fix is unclear, set suggestedCode to empty string `""` + +### Example Findings (Security Domain) + +```json +[ + { + "filePath": "Src/Layers/W1/Business Foundation/App/MyCodeunit.al", + "lineNumber": 15, + "severity": "Critical", + "issue": "Hardcoded API key in source code", + "recommendation": "Use IsolatedStorage.SetEncrypted() or Azure Key Vault instead of hardcoded credentials", + "suggestedCode": " ApiKey := GetSecretFromIsolatedStorage('ApiKey');" + }, + { + "filePath": "Src/Layers/W1/Business Foundation/App/CustomerPage.al", + "lineNumber": 89, + "severity": "High", + "issue": "Missing permission check before modifying customer data", + "recommendation": "Add InherentPermissions attribute or explicit permission validation before Modify", + "suggestedCode": " [InherentPermissions(PermissionObjectType::TableData, Database::Customer, 'M')]\n procedure UpdateCustomer(Rec: Record Customer)" + } +] +``` + +### Example Findings (Performance Domain) + +```json +[ + { + "filePath": "Src/Layers/W1/Business Foundation/App/PostingRoutine.al", + "lineNumber": 127, + "severity": "High", + "issue": "N+1 query pattern: FindSet without filtering, Get inside loop", + "recommendation": "Use SetRange to filter before FindSet, or restructure query to single batch operation", + "suggestedCode": " Customer.SetRange(\"Country/Region Code\", 'US');\n if Customer.FindSet() then\n repeat\n // Process customer\n until Customer.Next() = 0;" + } +] +``` + +### Example Findings (Style Domain) + +```json +[ + { + "filePath": "Src/Layers/W1/Business Foundation/App/MyCodeunit.al", + "lineNumber": 42, + "severity": "Medium", + "issue": "Inconsistent variable naming - uses 'CustName' instead of 'CustomerName'", + "recommendation": "Replace abbreviated variable name with full descriptive name matching codebase conventions", + "suggestedCode": " CustomerName := Record.Name;" + } +] +``` + +### Applying Localization Rules to Findings + +When you find an issue in a country-layer file under `Src/Layers` that also exists in W1: +- **Create the finding against W1 file path only**, not the country layer copy +- **Set lineNumber to the W1 line number** +- Country layer will be auto-synced with the W1 fix + +Example: +```json +[ + { + "filePath": "Src/Layers/W1/Business Foundation/App/TaxCalculation.al", + "lineNumber": 85, + "severity": "High", + "issue": "Missing validation trigger", + "recommendation": "Add OnValidate trigger to prevent invalid values", + "suggestedCode": " trigger OnValidate()\n begin\n TestField(Amount, '<>0');\n end;" + } +] +``` +(Note: Even if AT, BE, CH layers also have this issue, we report W1 only, and the sync script propagates the fix automatically.) diff --git a/src/bcbench/agent/shared/instructions/microsoft-BCApps/skills/al-test-generation/SKILL.md b/src/bcbench/agent/shared/instructions/microsoft-BCApps/skills/al-test-generation/SKILL.md deleted file mode 100644 index 6817e6319..000000000 --- a/src/bcbench/agent/shared/instructions/microsoft-BCApps/skills/al-test-generation/SKILL.md +++ /dev/null @@ -1,93 +0,0 @@ ---- -name: al-test-generation -description: Guide for creating AL tests for Microsoft Dynamics 365 Business Central. Use this when asked to write, create, or generate AL test codeunits, test procedures, or test automation for Business Central. ---- - -To create AL tests for Microsoft Dynamics 365 Business Central, follow this process: - -## 1. Analyze the Code Under Test - -Before writing any test code: -1. Read and understand the procedure or functionality being tested -2. Trace through all code paths to identify UI interactions -3. Examine table definitions for TableRelation constraints - -## 2. Identify Required Handler Methods - -**CRITICAL: Tests fail with "Unhandled UI" errors when handlers are missing.** - -Look for these patterns in the code under test: - -| Code Pattern | Required Handler | -| ------------------------------------- | --------------------------- | -| `Confirm()` | `[ConfirmHandler]` | -| `Message()` | `[MessageHandler]` | -| `StrMenu()` | `[StrMenuHandler]` | -| `Page.Run()` | `[PageHandler]` | -| `Page.RunModal()` | `[ModalPageHandler]` | -| `Report.Run()` or `Report.RunModal()` | `[ReportHandler]` | -| Report request page | `[RequestPageHandler]` | -| `Hyperlink()` | `[HyperlinkHandler]` | -| `Notification.Send()` | `[SendNotificationHandler]` | - -## 3. Analyze TableRelation Constraints - -**CRITICAL: Tests fail with validation errors when inserting data that violates TableRelation constraints.** - -Before inserting test data: -1. Read the table definition for all fields receiving values -2. Identify fields with `TableRelation` properties -3. Ensure related records exist before inserting test data -4. Use Library functions (e.g., `LibrarySales`, `LibraryPurchase`) to create prerequisite data - -## 4. Write Test Structure - -Follow the AAA pattern (Arrange-Act-Assert): - -```AL -[Test] -[HandlerFunctions('RequiredHandlers')] -procedure TestProcedureName() -begin - // [GIVEN] Setup test data and preconditions - Initialize(); - CreateTestData(); - - // [WHEN] Execute the action being tested - ExecuteAction(); - - // [THEN] Verify the expected results - VerifyResults(); -end; -``` - -## 5. Handler Method Signatures - -```AL -[ConfirmHandler] -procedure ConfirmHandlerYes(Question: Text[1024]; var Reply: Boolean) -begin - Reply := true; -end; - -[MessageHandler] -procedure MessageHandler(Message: Text[1024]) -begin - // Empty - suppresses message display -end; - -[ModalPageHandler] -procedure ModalPageHandler(var TestPage: TestPage "Page Name") -begin - TestPage.OK().Invoke(); -end; -``` - -## 6. Best Practices - -- Use descriptive test procedure names that explain what is being tested -- One assertion concept per test -- Use Library Variable Storage to pass data between handlers and tests -- Do NOT verify values inside handler procedures -- Clean up test data in teardown or use transaction rollback -- Use `Initialize()` procedure to set up common test fixtures