Skip to content

Implement Heretto CMS uploader for screenshot synchronization#414

Merged
hawkeyexl merged 10 commits intomainfrom
heretto2
Jan 5, 2026
Merged

Implement Heretto CMS uploader for screenshot synchronization#414
hawkeyexl merged 10 commits intomainfrom
heretto2

Conversation

@hawkeyexl
Copy link
Contributor

@hawkeyexl hawkeyexl commented Jan 4, 2026

Introduce a HerettoUploader class to automate the upload of changed screenshots to Heretto CMS. Enhance the screenshot saving logic to track changes and integrate metadata. Add documentation and improve error handling for upload operations. Update dependencies and implement integration tests for the new functionality.

Summary by CodeRabbit

  • New Features

    • Heretto CMS integration to automatically upload changed screenshots during test runs; uploadOnChange enabled by default.
    • Screenshot results now include change flags, screenshot path, and source-integration metadata for upload workflows.
  • Tests

    • Extensive integration and unit tests covering Heretto upload flows, dependency/folder resolution, and screenshot preservation/variation scenarios.
  • Chores

    • Updated multiple dependencies to newer minor/patch versions.

✏️ Tip: You can customize this high-level summary in your review settings.

…nization

- Added HerettoUploader class to handle file uploads to Heretto CMS.
- Integrated uploader into the existing upload process for changed screenshots.
- Enhanced screenshot saving logic to track changes and source integration metadata.
- Created documentation for Heretto screenshot sync feature, detailing the upload process and architecture.
- Updated resolver to build file mappings and handle Heretto-specific configurations.
- Introduced utility functions to find Heretto integrations based on file paths.
- Added error handling and logging for upload operations to improve reliability.
- Update `saveScreenshot.js` to mark new screenshots as changed for upload.
- Add a new test file `screenshot.test.js` to verify sourceIntegration preservation and changed status for screenshots.
- Create a new test file `upload.test.js` to validate the upload module, including collecting changed files and integration configuration.
- Modify `package-lock.json` to link to local `doc-detective-common` instead of a versioned package.
- Enhance `heretto.js` to retrieve resource dependencies from the Heretto REST API, including detailed logging and XML parsing.
… integration tests

- Refactor folder resolution logic to ensure correct target folder is identified before file upload.
- Implement integration tests for Heretto upload functionality, covering document retrieval and upload processes.
- Update existing tests to reflect changes in folder resolution behavior.
…tails

- Add screenshotPath to outputs when overwriting existing files
- Set changed flag to true when a file is overwritten
- Preserve sourceIntegration metadata in outputs if provided
- Bump appium-chromium-driver to version 2.1.0
- Bump appium-geckodriver to version 2.1.6
- Bump appium-safari-driver to version 4.1.6
- Update doc-detective-common to version 3.6.0-dev.2
- Update doc-detective-resolver to version 3.6.1-dev.2
- Upgrade posthog-node to version 5.18.1
- Upgrade webdriverio to version 9.23.0
- Change "doc-detective-common" and "doc-detective-resolver" versions to fixed versions
@coderabbitai
Copy link
Contributor

coderabbitai bot commented Jan 4, 2026

Note

Other AI code review bot(s) detected

CodeRabbit has detected other AI code review bot(s) in this pull request and will avoid duplicating their findings in the review comments. This may lead to a less comprehensive review.

Walkthrough

Adds a Heretto CMS uploader and an integrations registry, propagates sourceIntegration metadata for changed screenshots, triggers optional post-test uploads (uploadOnChange), and introduces unit/integration tests plus dependency bumps.

Changes

Cohort / File(s) Summary
Integrations core & Heretto uploader
src/integrations/index.js, src/integrations/heretto.js
New uploader registry and HerettoUploader: uploader discovery/registration, changed-file collection, upload orchestration, Heretto REST interactions (folder/doc resolution, create/search, upload), content-type & XML helpers.
Test runner hook & dev config
src/tests.js, dev/index.js
Post-test upload pass: calls uploadChangedFiles when config.integrations.heretto.uploadOnChange is true (default true) and attaches report.uploadResults. Dev config exposes uploadOnChange.
Screenshot result propagation
src/tests/saveScreenshot.js
Adds outputs.changed, consistently sets outputs.screenshotPath and outputs.sourceIntegration across overwrite/diff/new-file branches to mark files for upload.
Tests
test/heretto-upload.test.js, test/screenshot.test.js, test/upload.test.js
New integration/unit tests covering HerettoUploader methods, upload flows, sourceIntegration preservation, changed-file collection, path edge cases, and Windows/POSIX normalization.
Dependencies & package
package.json
Version bumps for Appium drivers, webdriverio, posthog-node, doc-detective packages, and related dev deps.

Sequence Diagram(s)

sequenceDiagram
    autonumber
    participant Runner as Test Runner
    participant SaveShot as saveScreenshot
    participant Integrations as Integrations Module
    participant Uploader as HerettoUploader
    participant Heretto as Heretto API
    participant FS as File System

    Runner->>SaveShot: execute screenshot step
    SaveShot->>SaveShot: diff/crop -> determine changed
    alt changed
        SaveShot-->>Runner: result (outputs.changed=true, outputs.sourceIntegration)
    else unchanged
        SaveShot-->>Runner: result (outputs.changed=false)
    end

    Runner->>Integrations: tests complete -> collectChangedFiles(report)
    Integrations->>Integrations: getUploader(sourceIntegration)
    loop per changed file
        Integrations->>Uploader: upload({config, integrationConfig, localFilePath,...})
        Uploader->>Uploader: resolveFromDependencies / findParentFolderFromDependencies
        Uploader->>Heretto: GET/SEARCH folders & documents
        Heretto-->>Uploader: metadata / IDs
        Uploader->>FS: read binary file
        FS-->>Uploader: file bytes
        Uploader->>Heretto: POST/PUT upload content
        Heretto-->>Uploader: upload response
        Uploader-->>Integrations: {status, description}
    end
    Integrations-->>Runner: aggregated uploadResults
Loading

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~45 minutes

Possibly related PRs

Poem

🐰 I hopped through pixels, light and spry,

Flagged the changes, gave each one a try.
I carried bytes on whiskers neat,
Popped them gently to Heretto's seat.
Hooray — the sync is snug and complete!

Pre-merge checks and finishing touches

✅ Passed checks (3 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title accurately summarizes the main objective: implementing a Heretto CMS uploader for screenshot synchronization, which is the central feature of this PR.
Docstring Coverage ✅ Passed Docstring coverage is 87.50% which is sufficient. The required threshold is 80.00%.
✨ Finishing touches
  • 📝 Generate docstrings

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

Comment @coderabbitai help to get the list of available commands and usage tips.

Copy link
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 4

🧹 Nitpick comments (9)
dev/index.js (1)

11-11: Remove commented developer-specific path.

This commented line contains a local absolute path that appears to be developer-specific debugging code and should be removed before merging.

🔎 Proposed fix
    input: ["heretto:example"],
-    // input: ["c:\\Users\\hawkeyexl\\AppData\\Local\\Temp\\doc-detective\\heretto_c96e625d5c1ee50972362046445a5ca4\\ot-output\\dita\\_topics\\espresso.dita"],
    logLevel: "debug",
src/tests.js (1)

801-817: Consider making the integration check more generic.

The upload logic currently checks specifically for config?.integrations?.heretto?.length > 0, but the uploadChangedFiles function appears to support multiple integration types through a registry pattern. Consider checking for any configured integrations that support upload rather than hardcoding Heretto.

🔎 Proposed refactor to support multiple integration types
-  // Upload changed files back to source integrations (best-effort)
-  // This automatically syncs any changed screenshots back to their source CMS
-  if (config?.integrations?.heretto?.length > 0) {
+  // Upload changed files back to source integrations (best-effort)
+  // This automatically syncs any changed screenshots back to their source CMS
+  if (config?.integrations && Object.keys(config.integrations).length > 0) {
    try {
      const uploadResults = await uploadChangedFiles({ config, report, log });
      report.uploadResults = uploadResults;
    } catch (error) {
      log(config, "warning", `Failed to upload changed files: ${error.message}`);
      report.uploadResults = {
        total: 0,
        successful: 0,
        failed: 0,
        skipped: 0,
        error: error.message,
      };
    }
  }
test/screenshot.test.js (1)

43-51: Follow coding guidelines for temporary file cleanup.

The cleanup pattern doesn't use try/finally blocks as recommended in the coding guidelines. While the current afterEach approach works, using try/finally within each test would ensure cleanup even if assertions fail.

Based on learnings, test files should use fs.writeFileSync() + fs.unlinkSync() in try/finally blocks for temporary test files to ensure cleanup.

Example pattern:

it("test case", async function () {
  const tempFilePath = path.join(tempDir, "test-spec.json");
  try {
    fs.writeFileSync(tempFilePath, JSON.stringify(testSpec, null, 2));
    const result = await runTests({ input: tempFilePath, logLevel: "silent" });
    // assertions...
  } finally {
    if (fs.existsSync(tempFilePath)) {
      fs.unlinkSync(tempFilePath);
    }
  }
});
test/heretto-upload.test.js (2)

128-162: Consider test isolation for document creation.

The test stores testDocumentId in a shared variable that's used by subsequent tests, creating interdependence. If this test fails, all subsequent tests that depend on testDocumentId will be skipped. Consider making tests more independent or using a before hook to set up shared test resources.

This is acceptable for integration tests but worth noting for maintainability.


279-377: Consider cleanup of test documents in Heretto.

The integration tests create documents in Heretto CMS (e.g., lines 314-332 create a file in the _media folder) but don't clean them up afterward. While this may be acceptable for a test environment, consider whether these test artifacts should be removed to prevent accumulation.

If cleanup is desired, you could track created document IDs and delete them in an after hook:

const createdDocIds = [];

after(async function () {
  if (hasCredentials && createdDocIds.length > 0) {
    // Delete test documents
    for (const docId of createdDocIds) {
      // await herettoUploader.deleteDocument({ ... });
    }
  }
});
test/upload.test.js (1)

308-309: Naming nit: HerettoUploader is an instance, not a class.

Using PascalCase (HerettoUploader) typically implies a class or constructor, but getUploader() returns an instance. Consider renaming to herettoUploader for clarity.

🔎 Suggested rename
 describe("HerettoUploader", function () {
-  const HerettoUploader = getUploader({ type: "heretto" });
+  const herettoUploader = getUploader({ type: "heretto" });

   describe("resolveFromDependencies", function () {
     // Update all references from HerettoUploader to herettoUploader
src/integrations/heretto.js (3)

380-395: Regex-based XML parsing may be fragile with attribute ordering variations.

The regex pattern assumes name attribute comes before id:

`<folder\\s+name="${escapedFolderName}"\\s+id="([^"]+)"`

If the API returns attributes in different order (e.g., <folder id="..." name="...">), the match will fail. Consider matching both orderings or using a more flexible pattern.

🔎 Suggested fix to handle attribute order variations
-              const folderMatch = data.match(new RegExp(`<folder\\s+name="${escapedFolderName}"\\s+id="([^"]+)"`, 'i'));
+              // Match folder with name attribute, handling attribute order variations
+              const folderRegex = new RegExp(
+                `<folder\\s+(?:[^>]*?\\s)?name="${escapedFolderName}"[^>]*?\\sid="([^"]+)"|` +
+                `<folder\\s+(?:[^>]*?\\s)?id="([^"]+)"[^>]*?\\sname="${escapedFolderName}"`,
+                'i'
+              );
+              const folderMatch = data.match(folderRegex);
               
               if (folderMatch && folderMatch[1]) {
-                log("debug", `Found child folder '${folderName}' with ID: ${folderMatch[1]}`);
-                resolve(folderMatch[1]);
+                const folderId = folderMatch[1] || folderMatch[2];
+                log("debug", `Found child folder '${folderName}' with ID: ${folderId}`);
+                resolve(folderId);

624-628: Fallback to first search result may upload to wrong folder.

When an exact folder name match isn't found, the code falls back to the first search result. This could lead to uploading files to an unintended folder if the search returns partial matches (e.g., searching for "media" returns "media-archive" first).

Consider returning null instead and letting the caller handle the ambiguity, or at minimum logging at "warning" level since this is a potentially incorrect resolution.

🔎 Suggested fix for safer behavior
               if (match) {
                 log("debug", `Found folder: ${folderName} with ID: ${match.uuid || match.id}`);
                 resolve(match.uuid || match.id);
               } else {
-                // Take first result as fallback
-                log("debug", `Exact folder match not found, using first result: ${result.searchResults[0].uuid || result.searchResults[0].id}`);
-                resolve(result.searchResults[0].uuid || result.searchResults[0].id);
+                // No exact match - return null to avoid uploading to wrong folder
+                log("warning", `No exact folder match for '${folderName}', found ${result.searchResults.length} partial matches`);
+                resolve(null);
               }

704-709: Same fallback concern applies to file search.

Similar to searchFolderByName, falling back to the first search result for files could upload content to the wrong file if multiple files share the same name in different folders.

🔎 Consistent with folder search suggestion
               if (match) {
                 resolve(match.uuid || match.id);
               } else {
-                // Take first result as fallback
-                resolve(result.searchResults[0].uuid || result.searchResults[0].id);
+                // No exact match - return null to avoid overwriting wrong file
+                log("warning", `No exact file match for '${filename}', found ${result.searchResults.length} partial matches`);
+                resolve(null);
               }
📜 Review details

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 99b63f3 and 18712c2.

⛔ Files ignored due to path filters (1)
  • package-lock.json is excluded by !**/package-lock.json
📒 Files selected for processing (9)
  • dev/index.js
  • package.json
  • src/integrations/heretto.js
  • src/integrations/index.js
  • src/tests.js
  • src/tests/saveScreenshot.js
  • test/heretto-upload.test.js
  • test/screenshot.test.js
  • test/upload.test.js
🧰 Additional context used
📓 Path-based instructions (4)
src/tests.js

📄 CodeRabbit inference engine (AGENTS.md)

src/tests.js: Add new driver-based step actions to the driverActions array in src/tests.js and implement case in runStep() switch statement
Use stepExecutionFailed flag to skip remaining steps in a context after first failure
Set Appium driver timeout to 10 minutes (newCommandTimeout: 600) for all driver-based steps
runSpecs() in src/tests.js orchestrates test execution flow: spec → test → context → step hierarchy

Files:

  • src/tests.js
src/**/*.js

📄 CodeRabbit inference engine (AGENTS.md)

src/**/*.js: Use log(config, level, message) for all logging, where level = "debug"|"info"|"warning"|"error"
Use getAvailableApps() to detect installed browsers instead of hardcoding browser paths

Files:

  • src/tests.js
  • src/integrations/heretto.js
  • src/tests/saveScreenshot.js
  • src/integrations/index.js
test/**/*.js

📄 CodeRabbit inference engine (AGENTS.md)

test/**/*.js: Use fs.writeFileSync() + fs.unlinkSync() in try/finally blocks for temporary test files to ensure cleanup
HTTP request tests must run against test server on port 8092 in test/server/

Files:

  • test/upload.test.js
  • test/heretto-upload.test.js
  • test/screenshot.test.js
src/tests/*.js

📄 CodeRabbit inference engine (AGENTS.md)

src/tests/*.js: Create new step type handlers as async functions exported from src/tests/[actionName].js
Step handlers must return object with { status: "PASS"|"FAIL"|"WARNING", description: string, outputs: {} }
Validate step schema before resolving to object and setting defaults in new step handlers
Always handle driver cleanup in try/finally blocks in browser automation code
Set step variables via step.variables = { MY_VAR: "$$response.body.token" } to store data as environment variables
Use setViewportSize() to calculate inner dimensions rather than directly setting window size in browser automation
Mark unsafe steps with step.unsafe = true and require config.allowUnsafeSteps = true to execute

Files:

  • src/tests/saveScreenshot.js
🧠 Learnings (13)
📚 Learning: 2025-12-03T00:21:10.834Z
Learnt from: CR
Repo: doc-detective/core PR: 0
File: AGENTS.md:0-0
Timestamp: 2025-12-03T00:21:10.834Z
Learning: Applies to test/**/*.js : Use `fs.writeFileSync()` + `fs.unlinkSync()` in try/finally blocks for temporary test files to ensure cleanup

Applied to files:

  • src/tests.js
  • test/upload.test.js
  • test/heretto-upload.test.js
  • test/screenshot.test.js
📚 Learning: 2025-12-03T00:21:10.834Z
Learnt from: CR
Repo: doc-detective/core PR: 0
File: AGENTS.md:0-0
Timestamp: 2025-12-03T00:21:10.834Z
Learning: Applies to src/tests/*.js : Create new step type handlers as async functions exported from `src/tests/[actionName].js`

Applied to files:

  • src/tests.js
📚 Learning: 2025-12-03T00:21:10.834Z
Learnt from: CR
Repo: doc-detective/core PR: 0
File: AGENTS.md:0-0
Timestamp: 2025-12-03T00:21:10.834Z
Learning: `src/tests.js` is the core orchestrator handling 600+ LOC of Appium/WebDriver management

Applied to files:

  • src/tests.js
  • test/upload.test.js
  • test/heretto-upload.test.js
  • test/screenshot.test.js
  • package.json
📚 Learning: 2025-12-03T00:21:10.834Z
Learnt from: CR
Repo: doc-detective/core PR: 0
File: AGENTS.md:0-0
Timestamp: 2025-12-03T00:21:10.834Z
Learning: Applies to src/tests.js : `runSpecs()` in `src/tests.js` orchestrates test execution flow: spec → test → context → step hierarchy

Applied to files:

  • src/tests.js
📚 Learning: 2025-12-03T00:21:10.834Z
Learnt from: CR
Repo: doc-detective/core PR: 0
File: AGENTS.md:0-0
Timestamp: 2025-12-03T00:21:10.834Z
Learning: Applies to src/index.js : `src/index.js` must export `runTests()` as the main entry point for the package

Applied to files:

  • src/tests.js
📚 Learning: 2025-12-03T00:21:10.834Z
Learnt from: CR
Repo: doc-detective/core PR: 0
File: AGENTS.md:0-0
Timestamp: 2025-12-03T00:21:10.834Z
Learning: Individual step types have dedicated handler implementations in `src/tests/` (e.g., `httpRequest.js`, `runShell.js`)

Applied to files:

  • src/tests.js
📚 Learning: 2025-12-03T00:21:10.834Z
Learnt from: CR
Repo: doc-detective/core PR: 0
File: AGENTS.md:0-0
Timestamp: 2025-12-03T00:21:10.834Z
Learning: Tests follow nested hierarchy: spec (file) → test → context (browser/platform combo) → step (action), with contexts running serially and steps skipping after first failure

Applied to files:

  • src/tests.js
📚 Learning: 2025-12-03T00:21:10.834Z
Learnt from: CR
Repo: doc-detective/core PR: 0
File: AGENTS.md:0-0
Timestamp: 2025-12-03T00:21:10.834Z
Learning: Applies to src/config.js : Browsers and drivers must match the current platform (detected via `puppeteer/browsers`) with Appium drivers installed for chromium, gecko, and safari

Applied to files:

  • src/tests.js
  • package.json
📚 Learning: 2025-12-03T00:21:10.834Z
Learnt from: CR
Repo: doc-detective/core PR: 0
File: AGENTS.md:0-0
Timestamp: 2025-12-03T00:21:10.834Z
Learning: Applies to src/tests.js : Add new driver-based step actions to the `driverActions` array in `src/tests.js` and implement case in `runStep()` switch statement

Applied to files:

  • src/tests.js
  • test/screenshot.test.js
📚 Learning: 2025-12-03T00:21:10.834Z
Learnt from: CR
Repo: doc-detective/core PR: 0
File: AGENTS.md:0-0
Timestamp: 2025-12-03T00:21:10.834Z
Learning: Applies to test/**/*.js : HTTP request tests must run against test server on port 8092 in `test/server/`

Applied to files:

  • test/screenshot.test.js
📚 Learning: 2025-12-03T00:21:10.834Z
Learnt from: CR
Repo: doc-detective/core PR: 0
File: AGENTS.md:0-0
Timestamp: 2025-12-03T00:21:10.834Z
Learning: Applies to src/tests/*.js : Use `setViewportSize()` to calculate inner dimensions rather than directly setting window size in browser automation

Applied to files:

  • test/screenshot.test.js
📚 Learning: 2025-12-03T00:21:10.834Z
Learnt from: CR
Repo: doc-detective/core PR: 0
File: AGENTS.md:0-0
Timestamp: 2025-12-03T00:21:10.834Z
Learning: Applies to src/tests/*.js : Mark unsafe steps with `step.unsafe = true` and require `config.allowUnsafeSteps = true` to execute

Applied to files:

  • test/screenshot.test.js
📚 Learning: 2025-12-03T00:21:10.834Z
Learnt from: CR
Repo: doc-detective/core PR: 0
File: AGENTS.md:0-0
Timestamp: 2025-12-03T00:21:10.834Z
Learning: Applies to src/tests/*.js : Step handlers must return object with `{ status: "PASS"|"FAIL"|"WARNING", description: string, outputs: {} }`

Applied to files:

  • src/tests/saveScreenshot.js
🧬 Code graph analysis (5)
test/upload.test.js (1)
test/heretto-upload.test.js (4)
  • assert (3-3)
  • require (4-4)
  • result (314-332)
  • mockLog (38-42)
test/heretto-upload.test.js (1)
test/upload.test.js (3)
  • HerettoUploader (309-309)
  • mockLog (312-312)
  • mockLog (431-431)
test/screenshot.test.js (1)
src/tests/saveScreenshot.js (1)
  • result (21-27)
src/tests/saveScreenshot.js (3)
src/tests/findElement.js (1)
  • result (15-19)
src/tests/runShell.js (2)
  • result (15-25)
  • fractionalDiff (141-144)
src/utils.js (1)
  • fractionalDiff (284-284)
package.json (1)
scripts/bump-sync-version-resolver.js (1)
  • main (21-102)
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (9)
  • GitHub Check: test (ubuntu-latest, 22)
  • GitHub Check: test (macos-latest, 22)
  • GitHub Check: test (macos-latest, 24)
  • GitHub Check: test (macos-latest, 20)
  • GitHub Check: test (windows-latest, 24)
  • GitHub Check: test (windows-latest, 20)
  • GitHub Check: test (ubuntu-latest, 24)
  • GitHub Check: test (windows-latest, 22)
  • GitHub Check: test (ubuntu-latest, 20)
🔇 Additional comments (25)
dev/index.js (1)

34-34: LGTM! New Heretto upload feature enabled.

The uploadOnChange: true flag correctly enables automatic upload of changed screenshots back to Heretto CMS, aligning with the PR's objectives.

src/tests.js (1)

29-29: LGTM! Clean integration import.

The import of uploadChangedFiles from the new integrations module is properly structured.

test/screenshot.test.js (4)

54-97: LGTM! Comprehensive test for new screenshot sourceIntegration.

The test correctly verifies that sourceIntegration metadata is preserved when creating new screenshots, and that the changed flag is set to true. The assertions cover all required fields.


99-169: LGTM! Thorough test for variation threshold handling.

The test properly validates the scenario where screenshot variation exceeds the threshold, verifying both the WARNING result and the preservation of sourceIntegration metadata with changed: true.


171-241: LGTM! Validates within-variation behavior correctly.

The test accurately checks that sourceIntegration is preserved when screenshots are within the variation threshold, with changed: false and a PASS result.


243-272: LGTM! Correctly validates absence of sourceIntegration.

The test appropriately confirms that sourceIntegration is not set when not provided in the screenshot configuration.

test/heretto-upload.test.js (5)

15-50: LGTM! Proper environment-based test gating.

The test suite correctly checks for required credentials and skips tests when unavailable, with clear messaging to developers. The debug logging via DEBUG_HERETTO is a nice touch.


52-92: LGTM! Comprehensive getDocumentInfo validation.

The tests thoroughly validate document retrieval, including verification of all expected metadata fields (id, name, folderUuid, uri, mimeType) and correct folder placement.


94-126: LGTM! Proper file lookup testing.

The tests correctly verify both successful file lookup and null return for non-existent files, covering positive and negative cases.


284-376: LGTM! Proper cleanup with try/finally pattern.

The test correctly uses try/finally blocks to ensure temporary files and directories are cleaned up regardless of test outcome, following the coding guidelines.

Based on learnings, test files should use fs.writeFileSync() + fs.unlinkSync() in try/finally blocks for temporary test files to ensure cleanup.


379-436: LGTM! Thorough validation of document updates.

The test comprehensively verifies updating existing documents, including content verification with PNG signature validation. Well structured.

package.json (1)

35-49: Upgrade appium-chromium-driver to address CVE-2016-10557 (insecure binary download vulnerability).

The installed version 2.1.0 is affected by CVE-2016-10557, which allows man-in-the-middle attacks during Chromedriver binary download over insecure HTTP, potentially enabling remote code execution. Upgrade to version 2.9.4 or later to remediate.

Other checked dependencies (appium-geckodriver, webdriverio, posthog-node, axios) are secure in their current versions.

⛔ Skipped due to learnings
Learnt from: CR
Repo: doc-detective/core PR: 0
File: AGENTS.md:0-0
Timestamp: 2025-12-03T00:21:10.834Z
Learning: Applies to src/config.js : Browsers and drivers must match the current platform (detected via `puppeteer/browsers`) with Appium drivers installed for chromium, gecko, and safari
Learnt from: CR
Repo: doc-detective/core PR: 0
File: AGENTS.md:0-0
Timestamp: 2025-12-03T00:21:10.834Z
Learning: `src/tests.js` is the core orchestrator handling 600+ LOC of Appium/WebDriver management
src/tests/saveScreenshot.js (5)

24-27: LGTM! Well-structured outputs initialization.

The outputs object correctly initializes with changed: false as the default, following the step handler convention of returning { status, description, outputs }. Based on learnings, this aligns with the expected step handler return structure.


296-301: Correct metadata propagation for overwrite scenario.

The overwrite path properly sets changed: true and preserves sourceIntegration when present, enabling the upload workflow to identify and process changed files.


373-378: Correct handling of above-variation case.

When the screenshot difference exceeds maxVariation, the code properly marks it as changed and preserves the integration metadata for subsequent upload processing.


384-388: Correct behavior for within-variation case.

When screenshots are within acceptable variation, changed correctly remains false (from initialization) while still preserving the sourceIntegration metadata for output consistency.


396-405: Good handling of new screenshot case.

The guard condition !result.outputs.screenshotPath ensures this block only executes for truly new screenshots. Marking new files as changed: true enables them to be uploaded to the CMS.

test/upload.test.js (1)

1-650: Comprehensive test coverage for the upload module.

The test suite thoroughly covers:

  • Edge cases for collectChangedFiles (null, empty, missing fields)
  • getIntegrationConfig lookup scenarios
  • getUploader registry resolution
  • HerettoUploader utility methods (resolveFromDependencies, findParentFolderFromDependencies, canHandle, getContentType, escapeXml)
  • Upload validation (missing config, credentials)

The tests use in-memory mock objects appropriately, avoiding the need for temporary file cleanup.

src/integrations/index.js (3)

79-191: Well-designed best-effort upload orchestration.

The function properly handles:

  • Missing uploaders (skipped, not failed)
  • Missing integration configs (skipped with reason)
  • Upload failures (caught and logged, continues processing)
  • Detailed result tracking with status per file

The logging follows the log(config, level, message) pattern per coding guidelines.


39-68: Correct report traversal for collecting changed files.

The function properly navigates the nested report structure and validates all required conditions before collecting a file. The inclusion of stepId, testId, and specId in the result provides useful context for debugging and tracking.


218-223: Good interface validation for uploader registration.

The validation ensures all registered uploaders implement the required canHandle and upload methods, maintaining type safety at runtime.

src/integrations/heretto.js (4)

520-527: Good handling of attribute order variations in file matching.

The dual-regex approach (nameIdMatch and idNameMatch) correctly handles both possible attribute orderings in the XML response.


34-228: Well-structured upload workflow with comprehensive fallback strategy.

The upload method implements a robust resolution chain:

  1. Try resource dependencies map (most accurate)
  2. Find parent folder from dependencies or via API
  3. Look for file in target folder
  4. Create new document if not found
  5. Fall back to global search as last resort

Each step includes appropriate logging for debugging failed uploads.


557-564: Correct XML escaping implementation.

The & character is correctly escaped first to prevent double-escaping other entities. All five standard XML entities are properly handled.


913-937: Comprehensive content type mapping.

The getContentType method covers common image formats and DITA/XML content types relevant to documentation workflows. The fallback to application/octet-stream for unknown extensions is appropriate.

- Set a 30-second timeout for various requests in HerettoUploader to prevent hanging.
- Update getIntegrationConfig to return null instead of undefined for unknown integrations.
- Increase timeout for screenshot tests to 60 seconds for better stability.
Copy link
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 3

♻️ Duplicate comments (1)
test/upload.test.js (1)

262-272: Verify assertion matches actual function behavior.

The test description says "returns null when heretto config not found by name" and line 271 asserts result equals null. However, if the underlying implementation uses Array.find() (which returns undefined when no match is found), this test may fail.

Verify that getIntegrationConfig explicitly returns null (not undefined) when no config is found:

#!/bin/bash
# Description: Check getIntegrationConfig implementation to verify return value

rg -A 10 "function getIntegrationConfig|getIntegrationConfig.*=" src/integrations/index.js
📜 Review details

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 18712c2 and 3efc7a6.

📒 Files selected for processing (4)
  • src/integrations/heretto.js
  • src/integrations/index.js
  • test/screenshot.test.js
  • test/upload.test.js
🚧 Files skipped from review as they are similar to previous changes (2)
  • src/integrations/index.js
  • src/integrations/heretto.js
🧰 Additional context used
📓 Path-based instructions (1)
test/**/*.js

📄 CodeRabbit inference engine (AGENTS.md)

test/**/*.js: Use fs.writeFileSync() + fs.unlinkSync() in try/finally blocks for temporary test files to ensure cleanup
HTTP request tests must run against test server on port 8092 in test/server/

Files:

  • test/upload.test.js
  • test/screenshot.test.js
🧠 Learnings (9)
📚 Learning: 2025-12-03T00:21:10.834Z
Learnt from: CR
Repo: doc-detective/core PR: 0
File: AGENTS.md:0-0
Timestamp: 2025-12-03T00:21:10.834Z
Learning: `src/tests.js` is the core orchestrator handling 600+ LOC of Appium/WebDriver management

Applied to files:

  • test/upload.test.js
  • test/screenshot.test.js
📚 Learning: 2025-12-03T00:21:10.834Z
Learnt from: CR
Repo: doc-detective/core PR: 0
File: AGENTS.md:0-0
Timestamp: 2025-12-03T00:21:10.834Z
Learning: Applies to test/**/*.js : Use `fs.writeFileSync()` + `fs.unlinkSync()` in try/finally blocks for temporary test files to ensure cleanup

Applied to files:

  • test/upload.test.js
  • test/screenshot.test.js
📚 Learning: 2025-12-03T00:21:10.834Z
Learnt from: CR
Repo: doc-detective/core PR: 0
File: AGENTS.md:0-0
Timestamp: 2025-12-03T00:21:10.834Z
Learning: Applies to test/**/*.js : HTTP request tests must run against test server on port 8092 in `test/server/`

Applied to files:

  • test/screenshot.test.js
📚 Learning: 2025-12-03T00:21:10.834Z
Learnt from: CR
Repo: doc-detective/core PR: 0
File: AGENTS.md:0-0
Timestamp: 2025-12-03T00:21:10.834Z
Learning: Applies to src/tests/*.js : Use `setViewportSize()` to calculate inner dimensions rather than directly setting window size in browser automation

Applied to files:

  • test/screenshot.test.js
📚 Learning: 2025-12-03T00:21:10.834Z
Learnt from: CR
Repo: doc-detective/core PR: 0
File: AGENTS.md:0-0
Timestamp: 2025-12-03T00:21:10.834Z
Learning: Applies to src/tests.js : Add new driver-based step actions to the `driverActions` array in `src/tests.js` and implement case in `runStep()` switch statement

Applied to files:

  • test/screenshot.test.js
📚 Learning: 2025-12-03T00:21:10.834Z
Learnt from: CR
Repo: doc-detective/core PR: 0
File: AGENTS.md:0-0
Timestamp: 2025-12-03T00:21:10.834Z
Learning: Applies to test/core.test.js : Test suite runs with mocha using `this.timeout(0)` for indefinite timeout in `test/core.test.js`

Applied to files:

  • test/screenshot.test.js
📚 Learning: 2025-12-03T00:21:10.834Z
Learnt from: CR
Repo: doc-detective/core PR: 0
File: AGENTS.md:0-0
Timestamp: 2025-12-03T00:21:10.834Z
Learning: Applies to src/tests.js : Set Appium driver timeout to 10 minutes (`newCommandTimeout: 600`) for all driver-based steps

Applied to files:

  • test/screenshot.test.js
📚 Learning: 2025-12-03T00:21:10.834Z
Learnt from: CR
Repo: doc-detective/core PR: 0
File: AGENTS.md:0-0
Timestamp: 2025-12-03T00:21:10.834Z
Learning: Applies to src/tests/*.js : Mark unsafe steps with `step.unsafe = true` and require `config.allowUnsafeSteps = true` to execute

Applied to files:

  • test/screenshot.test.js
📚 Learning: 2025-12-03T00:21:10.834Z
Learnt from: CR
Repo: doc-detective/core PR: 0
File: AGENTS.md:0-0
Timestamp: 2025-12-03T00:21:10.834Z
Learning: Applies to src/tests/*.js : Step handlers must return object with `{ status: "PASS"|"FAIL"|"WARNING", description: string, outputs: {} }`

Applied to files:

  • test/screenshot.test.js
🧬 Code graph analysis (1)
test/screenshot.test.js (4)
src/integrations/heretto.js (3)
  • fs (5-5)
  • require (9-9)
  • path (6-6)
src/tests/saveScreenshot.js (6)
  • fs (5-5)
  • require (1-1)
  • require (2-2)
  • require (3-3)
  • path (4-4)
  • saveScreenshot (20-379)
src/integrations/index.js (1)
  • require (10-10)
test/core.test.js (5)
  • screenshotPath (227-317)
  • require (419-825)
  • cleanup (451-465)
  • file (46-57)
  • cleanup (809-824)
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (10)
  • GitHub Check: Agent
  • GitHub Check: test (windows-latest, 22)
  • GitHub Check: test (macos-latest, 20)
  • GitHub Check: test (macos-latest, 24)
  • GitHub Check: test (macos-latest, 22)
  • GitHub Check: test (ubuntu-latest, 22)
  • GitHub Check: test (ubuntu-latest, 20)
  • GitHub Check: test (windows-latest, 20)
  • GitHub Check: test (ubuntu-latest, 24)
  • GitHub Check: test (windows-latest, 24)
🔇 Additional comments (4)
test/upload.test.js (4)

308-428: LGTM: Comprehensive test coverage for HerettoUploader path resolution.

The tests for resolveFromDependencies (lines 311-428) provide excellent coverage of edge cases including:

  • Null/undefined handling
  • Exact path matching
  • Relative path normalization
  • Filename+folder matching fallback
  • Filename-only matching as last resort
  • Internal key filtering (underscore prefix)
  • Windows backslash normalization

This thorough testing will help catch path-matching issues across different operating systems and directory structures.


430-524: LGTM: Thorough testing of folder resolution logic.

The tests for findParentFolderFromDependencies (lines 430-524) cover critical scenarios:

  • Null/undefined safety
  • Sibling file matching
  • Direct path matching
  • Ditamap fallback behavior
  • Preference ordering (sibling over fallback)
  • Relative path handling

This ensures robust folder resolution when uploading new files.


526-610: LGTM: Complete utility function coverage.

The tests for canHandle, getContentType, and escapeXml (lines 526-610) provide comprehensive validation:

  • Type validation for multiple integration types
  • Content-type mapping for common image formats
  • XML escaping for all special characters
  • Edge cases (null, undefined, empty strings, unknown extensions)

These utility tests ensure correct behavior for file handling and XML safety.


612-648: LGTM: Essential validation checks for upload.

The upload validation tests (lines 612-648) verify that the uploader fails gracefully with clear error messages when required configuration is missing (integrationConfig, organizationId, apiToken). This prevents confusing runtime errors during actual upload operations.

Copy link
Contributor

Copilot AI left a comment

Choose a reason for hiding this comment

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

Pull request overview

This PR implements a Heretto CMS uploader to automatically synchronize changed screenshots back to Heretto CMS. It introduces an extensible integration framework that can be expanded to support other CMS platforms in the future.

Key Changes:

  • Adds HerettoUploader class with folder resolution, file searching, document creation, and content upload capabilities
  • Enhances screenshot saving logic to track changes and preserve source integration metadata
  • Implements automatic upload of changed screenshots after test execution

Reviewed changes

Copilot reviewed 9 out of 10 changed files in this pull request and generated 11 comments.

Show a summary per file
File Description
src/integrations/index.js Provides integration uploader registry and orchestrates changed file uploads
src/integrations/heretto.js Implements Heretto-specific upload logic with resource dependency resolution and API interactions
src/tests/saveScreenshot.js Tracks screenshot changes and preserves sourceIntegration metadata in outputs
src/tests.js Triggers automatic upload of changed files after test completion for configured integrations
test/upload.test.js Unit tests for integration module functions
test/screenshot.test.js Integration tests for screenshot sourceIntegration preservation
test/heretto-upload.test.js Integration tests for Heretto upload functionality requiring live credentials
package.json Updates dependencies to newer versions
dev/index.js Adds uploadOnChange configuration flag to development config

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

/**
* Resolves a file path to its UUID using the resource dependencies map.
* @param {Object} options - Resolution options
* @returns {Object|null} File info with uuid and parentFolderId, or null if not found
Copy link

Copilot AI Jan 4, 2026

Choose a reason for hiding this comment

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

The JSDoc for this method is incomplete. It should document the structure of the options parameter, including the required properties: resourceDependencies, filePath, filename, and log. Additionally, the return value structure with uuid and parentFolderId properties should be documented.

Suggested change
* @returns {Object|null} File info with uuid and parentFolderId, or null if not found
* @param {Object.<string, {uuid: string, parentFolderId: string}>} options.resourceDependencies - Map of resource paths to resource metadata
* @param {string} options.filePath - Original file path to resolve
* @param {string} options.filename - Filename extracted from the file path
* @param {Function} options.log - Logging function
* @returns {{uuid: string, parentFolderId: string}|null} File info with uuid and parentFolderId, or null if not found

Copilot uses AI. Check for mistakes.
/**
* Collects all changed files from a test report that have source integrations.
* @param {Object} report - Test execution report
* @returns {Array<Object>} Array of { localPath, sourceIntegration } objects
Copy link

Copilot AI Jan 4, 2026

Choose a reason for hiding this comment

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

The JSDoc return type documentation is incomplete. The return array should include all properties that are pushed to the changedFiles array: localPath, sourceIntegration, stepId, testId, and specId. Update the JSDoc to reflect the complete structure.

Suggested change
* @returns {Array<Object>} Array of { localPath, sourceIntegration } objects
* @returns {Array<Object>} Array of objects with properties:
* - localPath
* - sourceIntegration
* - stepId
* - testId
* - specId

Copilot uses AI. Check for mistakes.
dev/index.js Outdated
async function main() {
const json = {
input: ["heretto:example"],
// input: ["c:\\Users\\hawkeyexl\\AppData\\Local\\Temp\\doc-detective\\heretto_c96e625d5c1ee50972362046445a5ca4\\ot-output\\dita\\_topics\\espresso.dita"],
Copy link

Copilot AI Jan 4, 2026

Choose a reason for hiding this comment

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

This commented-out line contains a hardcoded Windows absolute path that appears to be from a developer's local machine. This should be removed as it provides no value and could expose internal directory structure information.

Suggested change
// input: ["c:\\Users\\hawkeyexl\\AppData\\Local\\Temp\\doc-detective\\heretto_c96e625d5c1ee50972362046445a5ca4\\ot-output\\dita\\_topics\\espresso.dita"],

Copilot uses AI. Check for mistakes.
try {
// Parse XML to find the folder by name in children
// Looking for: <folder name="folderName" id="uuid"/>
const escapedFolderName = folderName.replace(/[.*+?^${}()|[\]\\]/g, '\\$&');
Copy link

Copilot AI Jan 4, 2026

Choose a reason for hiding this comment

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

The regex pattern uses single backslash for escaping, but in JavaScript strings, backslashes need to be doubled. The character class should use '\\' to properly escape the backslash character itself. This may cause issues when matching folder names containing backslashes.

Copilot uses AI. Check for mistakes.
// Parse XML to find the file by name
// Looking for child resources with matching name
// Example: <resource id="uuid" name="filename">...
const escapedFilename = filename.replace(/[.*+?^${}()|[\]\\]/g, '\\$&');
Copy link

Copilot AI Jan 4, 2026

Choose a reason for hiding this comment

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

The regex pattern uses single backslash for escaping, but in JavaScript strings, backslashes need to be doubled. The character class should use '\\' to properly escape the backslash character itself. This may cause issues when matching filenames containing backslashes.

Copilot uses AI. Check for mistakes.
* @param {Object} options.config - Doc Detective config
* @param {Object} options.report - Test execution report
* @param {Function} options.log - Logging function
* @returns {Promise<Object>} Upload results summary
Copy link

Copilot AI Jan 4, 2026

Choose a reason for hiding this comment

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

The JSDoc return type documentation is incomplete. The return object structure should document all properties: total, successful, failed, skipped, and details (which is an array of objects with localPath, status, and description/reason properties). Update the JSDoc to reflect the complete structure.

Suggested change
* @returns {Promise<Object>} Upload results summary
* @returns {Promise<{
* total: number,
* successful: number,
* failed: number,
* skipped: number,
* details: Array<{
* localPath: string,
* status: string,
* description?: string,
* reason?: string
* }>
* }>} Upload results summary

Copilot uses AI. Check for mistakes.
Comment on lines +98 to +182
for (const file of changedFiles) {
const uploader = getUploader(file.sourceIntegration);

if (!uploader) {
log(
config,
"warning",
`No uploader found for integration type: ${file.sourceIntegration.type}`
);
results.skipped++;
results.details.push({
localPath: file.localPath,
status: "SKIPPED",
reason: `No uploader for type: ${file.sourceIntegration.type}`,
});
continue;
}

// Get the integration config for API credentials
const integrationConfig = getIntegrationConfig(
config,
file.sourceIntegration
);

if (!integrationConfig) {
log(
config,
"warning",
`No integration config found for: ${file.sourceIntegration.integrationName}`
);
results.skipped++;
results.details.push({
localPath: file.localPath,
status: "SKIPPED",
reason: `No integration config found for: ${file.sourceIntegration.integrationName}`,
});
continue;
}

try {
log(
config,
"info",
`Uploading ${file.localPath} to ${file.sourceIntegration.type}...`
);

const uploadResult = await uploader.upload({
config,
integrationConfig,
localFilePath: file.localPath,
sourceIntegration: file.sourceIntegration,
log,
});

if (uploadResult.status === "PASS") {
results.successful++;
log(config, "info", `Successfully uploaded: ${file.localPath}`);
} else {
results.failed++;
log(
config,
"warning",
`Failed to upload ${file.localPath}: ${uploadResult.description}`
);
}

results.details.push({
localPath: file.localPath,
status: uploadResult.status,
description: uploadResult.description,
});
} catch (error) {
results.failed++;
log(
config,
"warning",
`Error uploading ${file.localPath}: ${error.message}`
);
results.details.push({
localPath: file.localPath,
status: "FAIL",
description: error.message,
});
}
}
Copy link

Copilot AI Jan 4, 2026

Choose a reason for hiding this comment

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

The uploads are performed sequentially in a for loop. For better performance when uploading multiple files, consider using Promise.all() or Promise.allSettled() to upload files in parallel. This would significantly reduce the total upload time when there are multiple changed files.

Copilot uses AI. Check for mistakes.
src/tests.js Outdated

// Upload changed files back to source integrations (best-effort)
// This automatically syncs any changed screenshots back to their source CMS
if (config?.integrations?.heretto?.length > 0) {
Copy link

Copilot AI Jan 4, 2026

Choose a reason for hiding this comment

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

The upload functionality is automatically triggered for all tests when any Heretto integration is configured, but there is no configuration option to control this behavior. Consider checking the uploadOnChange flag mentioned in dev/index.js to allow users to opt-in or opt-out of automatic uploads.

Suggested change
if (config?.integrations?.heretto?.length > 0) {
const uploadOnChange = config?.uploadOnChange ?? true;
if (uploadOnChange && config?.integrations?.heretto?.length > 0) {

Copilot uses AI. Check for mistakes.
if (!resourceDependencies) return null;

// Normalize the file path for comparison
const normalizedPath = filePath.replace(/\\/g, "/").replace(/^\.\//, "").replace(/^\.\.\//, "");
Copy link

Copilot AI Jan 4, 2026

Choose a reason for hiding this comment

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

The path normalization only removes leading './' and '../' once, but paths could have multiple levels of relative references like '../../folder/file.png'. Consider using a more robust path normalization method or a loop to handle all relative path segments.

Suggested change
const normalizedPath = filePath.replace(/\\/g, "/").replace(/^\.\//, "").replace(/^\.\.\//, "");
const normalizedPath = path.posix
.normalize(filePath.replace(/\\/g, "/"))
.replace(/^(\.\/|(\.\.\/))+/g, "");

Copilot uses AI. Check for mistakes.
if (!resourceDependencies) return result;

// Normalize path and get parent directory
const normalizedPath = filePath.replace(/\\/g, "/").replace(/^\.\//, "").replace(/^\.\.\//, "");
Copy link

Copilot AI Jan 4, 2026

Choose a reason for hiding this comment

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

The path normalization only removes leading './' and '../' once, but paths could have multiple levels of relative references like '../../folder/file.png'. Consider using a more robust path normalization method or a loop to handle all relative path segments.

Suggested change
const normalizedPath = filePath.replace(/\\/g, "/").replace(/^\.\//, "").replace(/^\.\.\//, "");
const normalizedPath = path.posix.normalize(filePath.replace(/\\/g, "/"));

Copilot uses AI. Check for mistakes.
- Remove hardcoded input path in main function for flexibility.
- Update file path normalization to use posix for cross-platform support.
- Expand documentation for resolveFromDependencies and findParentFolderFromDependencies methods.
- Add additional properties to collectChangedFiles return type for better clarity.
- Implement parallel uploads in uploadChangedFiles for improved performance.
- Modify screenshot tests to ensure consistent port usage and cleanup of temporary files.
Copy link
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 0

🧹 Nitpick comments (1)
src/integrations/heretto.js (1)

312-314: Consider simplifying the path normalization regex.

The current regex .replace(/^(\.\/)|(\.\.\/)*/g, "") works but has a subtle issue: (\.\.\/)* can match an empty string, causing unnecessary empty matches. A cleaner pattern would be:

🔎 Suggested improvement
     const normalizedPath = path.posix
       .normalize(filePath.replace(/\\/g, "/"))
-      .replace(/^(\.\/)|(\.\.\/)*/g, "");
+      .replace(/^(\.\.?\/)+/g, "");

This matches one or more occurrences of either ./ or ../ at the start, avoiding empty matches.

📜 Review details

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 3efc7a6 and 8abf499.

📒 Files selected for processing (5)
  • dev/index.js
  • src/integrations/heretto.js
  • src/integrations/index.js
  • src/tests.js
  • test/screenshot.test.js
🚧 Files skipped from review as they are similar to previous changes (3)
  • test/screenshot.test.js
  • src/tests.js
  • dev/index.js
🧰 Additional context used
📓 Path-based instructions (1)
src/**/*.js

📄 CodeRabbit inference engine (AGENTS.md)

src/**/*.js: Use log(config, level, message) for all logging, where level = "debug"|"info"|"warning"|"error"
Use getAvailableApps() to detect installed browsers instead of hardcoding browser paths

Files:

  • src/integrations/heretto.js
  • src/integrations/index.js
🧠 Learnings (1)
📚 Learning: 2025-12-03T00:21:10.834Z
Learnt from: CR
Repo: doc-detective/core PR: 0
File: AGENTS.md:0-0
Timestamp: 2025-12-03T00:21:10.834Z
Learning: Applies to src/tests/*.js : Step handlers must return object with `{ status: "PASS"|"FAIL"|"WARNING", description: string, outputs: {} }`

Applied to files:

  • src/integrations/index.js
🧬 Code graph analysis (2)
src/integrations/heretto.js (1)
test/heretto-upload.test.js (4)
  • path (2-2)
  • content (215-221)
  • content (263-269)
  • content (423-429)
src/integrations/index.js (2)
src/integrations/heretto.js (1)
  • require (9-9)
test/upload.test.js (13)
  • sourceIntegration (255-255)
  • sourceIntegration (268-268)
  • sourceIntegration (276-276)
  • report (26-57)
  • report (71-100)
  • report (107-132)
  • report (139-167)
  • report (174-224)
  • config (236-236)
  • config (242-242)
  • config (254-254)
  • config (263-267)
  • config (275-275)
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (9)
  • GitHub Check: test (macos-latest, 24)
  • GitHub Check: test (macos-latest, 20)
  • GitHub Check: test (macos-latest, 22)
  • GitHub Check: test (windows-latest, 20)
  • GitHub Check: test (windows-latest, 24)
  • GitHub Check: test (ubuntu-latest, 22)
  • GitHub Check: test (ubuntu-latest, 24)
  • GitHub Check: test (windows-latest, 22)
  • GitHub Check: test (ubuntu-latest, 20)
🔇 Additional comments (16)
src/integrations/heretto.js (11)

1-23: LGTM!

The class declaration and canHandle method are well-structured. The optional chaining on sourceIntegration?.type provides safe null handling.


34-228: Well-structured upload orchestration with comprehensive error handling.

The multi-step resolution strategy (dependencies → ditamap parent → folder search → global search) provides good fallback coverage. The logging wrapper pattern at lines 66-67 correctly adapts the internal log(level, msg) calls to the required log(config, level, msg) format per coding guidelines.


239-291: LGTM!

The three-tier resolution strategy (exact path → filename+folder → filename-only) provides good fallback behavior. The path normalization using path.posix.normalize() correctly handles cross-platform path differences.


359-424: LGTM!

Timeout handling is properly implemented. The regex escape pattern at line 391 is correct—in a JavaScript regex literal, [\]\\] correctly matches either ] or \ characters. The graceful degradation via resolve(null) on errors is appropriate for fallback-based resolution.


431-499: LGTM!

The document creation handles edge cases well, including the "already exists" scenario. The XML escaping via escapeXml() prevents injection, and timeout handling is properly placed.


506-574: LGTM!

The dual regex approach at lines 540-541 handles XML attribute ordering flexibility well. Timeout and error handling follow the established pattern.


581-588: LGTM!

The XML escaping correctly handles all five predefined XML entities in the proper order (ampersand first to prevent double-escaping).


648-651: Verify fallback behavior for non-exact folder matches.

When an exact folder name match isn't found, the code falls back to the first search result. This could potentially target an incorrect folder if the search returns partial matches.

Consider whether logging a warning here would help debugging when uploads go to unexpected locations.


770-820: LGTM!

The upload implementation correctly handles binary content via Buffer and includes proper timeout handling.


827-960: LGTM!

The XML parsing approach at lines 862-877 is reasonable for a known schema. Binary content handling in getDocumentContent correctly uses Buffer.concat(chunks).


967-986: LGTM!

The content type mapping covers the common formats for screenshots and DITA content. The application/octet-stream fallback is appropriate for unknown types.

src/integrations/index.js (5)

1-32: LGTM!

The registry pattern with getUploader provides a clean, extensible mechanism for adding new integration uploaders. The null safety with optional chaining is appropriate.


34-73: LGTM!

The report traversal correctly extracts changed files with source integrations. The JSDoc at lines 37-43 properly documents the return structure.


95-240: LGTM!

The parallel upload implementation using Promise.allSettled is well-designed. The internal error catching at lines 185-188 preserves file references for proper result aggregation. Logging follows the required log(config, level, message) format per coding guidelines.


242-261: LGTM!

The nullish coalescing operator at line 257 ensures consistent null returns when no config is found, addressing the previous review feedback.


263-280: LGTM!

The interface validation at lines 268-270 ensures registered uploaders conform to the expected contract before being added to the registry.

- Normalize file paths to handle multiple levels of relative references.
- Update documentation for resource dependencies and upload functions.
- Check both global and per-integration uploadOnChange settings for better control.
Copy link
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 0

🧹 Nitpick comments (3)
src/tests.js (1)

801-824: Implementation addresses the uploadOnChange control requirement.

The logic correctly checks both global config.uploadOnChange and per-integration uploadOnChange settings, with sensible defaults (true for backward compatibility). Error handling with try/catch and warning logging is appropriate.

Minor: The condition on line 810 has a redundant check—herettoConfigs.length > 0 is implied when hasUploadEnabledIntegration is true (since .some() returns false for empty arrays).

🔎 Optional simplification
-  if (globalUploadOnChange && hasUploadEnabledIntegration && herettoConfigs.length > 0) {
+  if (globalUploadOnChange && hasUploadEnabledIntegration) {
src/integrations/index.js (1)

159-226: Parallel upload execution with Promise.allSettled looks good.

The approach of catching errors within each promise to preserve the file reference is smart. The comment on line 189 correctly explains why all promises should be fulfilled.

One minor defensive consideration: while the current implementation ensures all promises resolve (not reject), directly accessing settled.value without checking settled.status === 'fulfilled' could be fragile if future refactoring changes the error-handling pattern.

🔎 Optional defensive check
     for (const settled of settledResults) {
-      // All promises should be fulfilled since we catch errors internally
-      const { file, uploadResult, error } = settled.value;
+      // All promises should be fulfilled since we catch errors internally
+      if (settled.status !== 'fulfilled') continue; // Defensive guard
+      const { file, uploadResult, error } = settled.value;
src/integrations/heretto.js (1)

608-691: Minor concern: Fallback to first search result may return incorrect folder.

Lines 662-664 fall back to the first search result when no exact name match is found. This could silently select the wrong folder if the search returns multiple results with similar names.

Consider either:

  1. Returning null instead of the first result to force more explicit resolution
  2. Adding a warning log when using the fallback

The current approach is acceptable since it's logged at debug level and the overall upload will still be tracked.

🔎 Optional: More explicit fallback handling
                 if (match) {
                   log("debug", `Found folder: ${folderName} with ID: ${match.uuid || match.id}`);
                   resolve(match.uuid || match.id);
                 } else {
-                  // Take first result as fallback
-                  log("debug", `Exact folder match not found, using first result: ${result.searchResults[0].uuid || result.searchResults[0].id}`);
-                  resolve(result.searchResults[0].uuid || result.searchResults[0].id);
+                  // Take first result as fallback (may not be the intended folder)
+                  log("warning", `Exact folder match not found for '${folderName}', using first result: ${result.searchResults[0].uuid || result.searchResults[0].id}`);
+                  resolve(result.searchResults[0].uuid || result.searchResults[0].id);
                 }
📜 Review details

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 8abf499 and 7e030a3.

📒 Files selected for processing (3)
  • src/integrations/heretto.js
  • src/integrations/index.js
  • src/tests.js
🧰 Additional context used
📓 Path-based instructions (2)
src/tests.js

📄 CodeRabbit inference engine (AGENTS.md)

src/tests.js: Add new driver-based step actions to the driverActions array in src/tests.js and implement case in runStep() switch statement
Use stepExecutionFailed flag to skip remaining steps in a context after first failure
Set Appium driver timeout to 10 minutes (newCommandTimeout: 600) for all driver-based steps
runSpecs() in src/tests.js orchestrates test execution flow: spec → test → context → step hierarchy

Files:

  • src/tests.js
src/**/*.js

📄 CodeRabbit inference engine (AGENTS.md)

src/**/*.js: Use log(config, level, message) for all logging, where level = "debug"|"info"|"warning"|"error"
Use getAvailableApps() to detect installed browsers instead of hardcoding browser paths

Files:

  • src/tests.js
  • src/integrations/heretto.js
  • src/integrations/index.js
🧠 Learnings (10)
📚 Learning: 2025-12-03T00:21:10.834Z
Learnt from: CR
Repo: doc-detective/core PR: 0
File: AGENTS.md:0-0
Timestamp: 2025-12-03T00:21:10.834Z
Learning: Applies to test/**/*.js : Use `fs.writeFileSync()` + `fs.unlinkSync()` in try/finally blocks for temporary test files to ensure cleanup

Applied to files:

  • src/tests.js
📚 Learning: 2025-12-03T00:21:10.834Z
Learnt from: CR
Repo: doc-detective/core PR: 0
File: AGENTS.md:0-0
Timestamp: 2025-12-03T00:21:10.834Z
Learning: Applies to src/tests/*.js : Create new step type handlers as async functions exported from `src/tests/[actionName].js`

Applied to files:

  • src/tests.js
📚 Learning: 2025-12-03T00:21:10.834Z
Learnt from: CR
Repo: doc-detective/core PR: 0
File: AGENTS.md:0-0
Timestamp: 2025-12-03T00:21:10.834Z
Learning: Applies to src/tests.js : Add new driver-based step actions to the `driverActions` array in `src/tests.js` and implement case in `runStep()` switch statement

Applied to files:

  • src/tests.js
📚 Learning: 2025-12-03T00:21:10.834Z
Learnt from: CR
Repo: doc-detective/core PR: 0
File: AGENTS.md:0-0
Timestamp: 2025-12-03T00:21:10.834Z
Learning: Applies to src/tests.js : `runSpecs()` in `src/tests.js` orchestrates test execution flow: spec → test → context → step hierarchy

Applied to files:

  • src/tests.js
📚 Learning: 2025-12-03T00:21:10.834Z
Learnt from: CR
Repo: doc-detective/core PR: 0
File: AGENTS.md:0-0
Timestamp: 2025-12-03T00:21:10.834Z
Learning: `src/tests.js` is the core orchestrator handling 600+ LOC of Appium/WebDriver management

Applied to files:

  • src/tests.js
📚 Learning: 2025-12-03T00:21:10.834Z
Learnt from: CR
Repo: doc-detective/core PR: 0
File: AGENTS.md:0-0
Timestamp: 2025-12-03T00:21:10.834Z
Learning: Applies to src/index.js : `src/index.js` must export `runTests()` as the main entry point for the package

Applied to files:

  • src/tests.js
📚 Learning: 2025-12-03T00:21:10.834Z
Learnt from: CR
Repo: doc-detective/core PR: 0
File: AGENTS.md:0-0
Timestamp: 2025-12-03T00:21:10.834Z
Learning: Individual step types have dedicated handler implementations in `src/tests/` (e.g., `httpRequest.js`, `runShell.js`)

Applied to files:

  • src/tests.js
📚 Learning: 2025-12-03T00:21:10.834Z
Learnt from: CR
Repo: doc-detective/core PR: 0
File: AGENTS.md:0-0
Timestamp: 2025-12-03T00:21:10.834Z
Learning: Tests follow nested hierarchy: spec (file) → test → context (browser/platform combo) → step (action), with contexts running serially and steps skipping after first failure

Applied to files:

  • src/tests.js
📚 Learning: 2025-12-03T00:21:10.834Z
Learnt from: CR
Repo: doc-detective/core PR: 0
File: AGENTS.md:0-0
Timestamp: 2025-12-03T00:21:10.834Z
Learning: Applies to src/config.js : Browsers and drivers must match the current platform (detected via `puppeteer/browsers`) with Appium drivers installed for chromium, gecko, and safari

Applied to files:

  • src/tests.js
📚 Learning: 2025-12-03T00:21:10.834Z
Learnt from: CR
Repo: doc-detective/core PR: 0
File: AGENTS.md:0-0
Timestamp: 2025-12-03T00:21:10.834Z
Learning: Applies to src/tests/*.js : Step handlers must return object with `{ status: "PASS"|"FAIL"|"WARNING", description: string, outputs: {} }`

Applied to files:

  • src/integrations/index.js
🧬 Code graph analysis (1)
src/integrations/index.js (2)
src/integrations/heretto.js (1)
  • require (9-9)
src/tests.js (18)
  • require (4-4)
  • require (6-6)
  • require (7-7)
  • require (8-8)
  • require (9-9)
  • require (10-10)
  • require (11-11)
  • require (12-12)
  • require (13-13)
  • require (14-14)
  • require (15-15)
  • require (16-16)
  • require (17-17)
  • require (18-18)
  • require (19-19)
  • report (383-411)
  • config (368-368)
  • config (1001-1001)
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (9)
  • GitHub Check: test (macos-latest, 24)
  • GitHub Check: test (macos-latest, 20)
  • GitHub Check: test (macos-latest, 22)
  • GitHub Check: test (windows-latest, 20)
  • GitHub Check: test (ubuntu-latest, 22)
  • GitHub Check: test (ubuntu-latest, 24)
  • GitHub Check: test (windows-latest, 24)
  • GitHub Check: test (windows-latest, 22)
  • GitHub Check: test (ubuntu-latest, 20)
🔇 Additional comments (18)
src/tests.js (1)

29-29: LGTM!

Import added correctly for the new integrations module.

src/integrations/index.js (4)

10-32: LGTM!

The uploader registry pattern is well-implemented with a clean iteration approach. Good early return when sourceIntegration?.type is missing.


44-73: LGTM!

The traversal logic correctly navigates the nested report structure and collects changed files with all necessary metadata. Good use of null-safe iteration with || [].


243-256: LGTM!

Consistent null return across all code paths (past review addressed with ?? null on line 252). The switch statement is well-structured for future integration type expansion.


262-267: LGTM!

Good runtime validation ensuring uploaders implement the required interface before registration.

src/integrations/heretto.js (13)

14-49: LGTM!

Clean interface implementation with good early validation of required configuration. Descriptive error messages will help users troubleshoot configuration issues.


78-198: Comprehensive file resolution with multiple fallback strategies.

The multi-step resolution approach (dependencies → folder API → global search) is well-designed for robustness. The race condition handling at lines 157-172 is a good defensive measure.

The complexity is justified given the various ways files might need to be located in the CMS.


239-295: LGTM!

The path normalization using path.posix.normalize combined with regex cleanup handles multiple levels of relative references correctly. The three-tier matching strategy (exact → folder+filename → filename-only) provides good flexibility for resolving files.


297-363: LGTM!

Well-documented function with consistent path normalization. The sibling-file lookup strategy is a clever way to find parent folder IDs when the exact file isn't in dependencies.


370-436: LGTM!

Good implementation with:

  • Proper regex escaping for special characters in folder names (line 403)
  • 30-second timeout handling (lines 424-427, addresses past review)
  • Appropriate error handling that resolves null rather than rejecting

443-511: LGTM!

Well-structured document creation with:

  • Proper XML escaping for filenames
  • Graceful handling of "already exists" conflicts
  • Timeout handling in place

The req.setTimeout + req.on("error") pattern correctly handles timeouts.


518-587: LGTM!

Good defensive approach with two regex patterns to handle different XML attribute orderings. Consistent timeout and error handling.


594-601: LGTM!

Standard and complete XML entity escaping.


698-776: LGTM!

Consistent implementation with searchFolderByName. Since this is used as a "last resort" global search (line 179), the fallback to first result is acceptable here.


783-833: LGTM!

Clean upload implementation with proper Content-Length header for binary content and consistent timeout handling.


840-920: LGTM!

Good handling of the XML response structure, correctly distinguishing between attributes and child elements. Including rawXml in the response is helpful for debugging.


927-973: LGTM!

Correct binary content handling using Buffer.concat for chunks. Consistent timeout and error handling pattern.


980-999: LGTM!

Good coverage of common file types with a safe fallback. The inclusion of .dita and .ditamap extensions is appropriate for the Heretto CMS use case.

@hawkeyexl hawkeyexl merged commit be8c485 into main Jan 5, 2026
16 checks passed
@hawkeyexl hawkeyexl deleted the heretto2 branch January 5, 2026 00:08
@promptless
Copy link

promptless bot commented Jan 5, 2026

📝 Documentation updates detected!

Updated existing suggestion: Document Heretto CMS integration for DITA content testing

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