I'm building a test loop that requires me to reset the document to original state with a clause with no tracked changes. Documenting how I got it to work as it was a major pain.

Summary of the Solution

The solution uses a 4-step process:

The Working Approach

  1. Save OOXML from clean document
  • Accept all revisions first
  • Disable track changes mode
  • Save OOXML (represents clean state)
  1. Restore OOXML
  • Insert OOXML back into document
  • May still contain tracked changes markup
  1. Extract clean text (key step)
  • Use getReviewedText(Word.ChangeTrackingVersion.current)
  • Returns text with all tracked changes applied
  • This is the clean version
  1. Replace document with clean text
  • Replace entire document with the clean text
  • Removes all tracked changes markup
  • Disable track changes mode

Why It Works

  • OOXML preserves document structure and formatting
  • getReviewedText() applies all tracked changes to produce clean text
  • Replacing the document with clean text removes tracked changes markup
  • Result: clean document without tracked changes

The documentation includes:

  • Problem statement
  • Failed attempts and why they failed
  • Complete working solution with code examples
  • Flow diagram
  • API methods reference
  • Error handling strategies
  • Performance considerations

Pitfalls & Problems Encountered (And How We Solved Them)

Pitfall 1: OOXML Contains Tracked Changes Markup

Problem:

  • When saving OOXML from a document with tracked changes, the OOXML itself contains the tracked changes markup
  • Restoring this OOXML brings back the tracked changes, making them visible again
  • Simply calling acceptAllRevisions() after restore doesn't work because the markup is embedded in the OOXML

Symptoms:

  • Document restored but tracked changes still visible
  • acceptAllRevisions() fails silently or doesn't clear changes
  • Validation fails because document doesn't match original

Solution:

  1. Accept revisions BEFORE saving OOXML - ensures OOXML is saved from clean state
  2. Extract clean text AFTER restore - use getReviewedText() to get text with all changes applied
  3. Replace document with clean text - removes tracked changes markup completely

Code:vascript
// Before save: Accept revisions first
context.document.acceptAllRevisions();
await context.sync();

// After restore: Extract clean text and replace
const cleanText = bodyRange.getReviewedText(Word.ChangeTrackingVersion.current).value;
cleanBodyRange.insertText(cleanText, Word.InsertLocation.replace);---

Pitfall 2: acceptAllRevisions() Failing Silently

Problem:

  • acceptAllRevisions() was being called but failing without throwing errors
  • No error messages in logs, making debugging difficult
  • Tracked changes remained even after calling the method

Symptoms:

  • Logs show "Could Not Accept Tracked Changes" warnings
  • No error codes or detailed error messages
  • Tracked changes persist after method call

Solution:

  1. Wrap in try-catch with detailed logging - capture error details
  2. Don't rely solely on acceptAllRevisions() - use clean text extraction instead
  3. Use getReviewedText() as primary method - more reliable than accepting revisions

Code:pt
try {
context.document.acceptAllRevisions();
await context.sync();
} catch (acceptError) {
logger.structured('warning', 'Could Not Accept Tracked Changes', {
error: acceptError.message,
errorCode: acceptError.code,
errorName: acceptError.name,
// Continue with alternative approach
});
}
// Use getReviewedText() instead - more reliable---

Pitfall 3: Tracked Changes Mode Not Disabled

Problem:

  • Even after removing tracked changes, Word may still be in "track changes" mode
  • New edits immediately create new tracked changes
  • Document appears clean but new changes are tracked

Symptoms:

  • Document restored cleanly
  • But next edit creates tracked changes immediately
  • Track changes indicator still active in Word UI

Solution:

  1. Disable track changes mode explicitly - use changeTrackingMode = Word.ChangeTrackingMode.off
  2. Fallback to trackRevisions = false - if ChangeTrackingMode not available
  3. Do this AFTER clean text replacement - ensures mode is disabled on clean document

Code:t
// Disable track changes mode
if (Word.ChangeTrackingMode) {
context.document.changeTrackingMode = Word.ChangeTrackingMode.off;
} else {
context.document.trackRevisions = false; // Fallback
}
await context.sync();

Pitfall 4: Validation Failures After Restoration

Problem:

  • Document restored but validation fails
  • Document text doesn't match originalDocumentText
  • May be due to whitespace differences, Unicode normalization, or incomplete restoration

Symptoms:

  • "Original Test Clause Validation Failed" errors
  • Length mismatches in logs
  • Test runs fail validation step

Solution:

  1. Normalize whitespace for comparison - use .trim().replace(/\s+/g, ' ')
  2. Validate at multiple points - after restore AND before LLM call
  3. Use getReviewedText() for clean copy - ensures we're comparing clean text
  4. Detailed error logging - log expected vs actual lengths and previews

Code:avascript
// Normalize for comparison
const normalizedCurrent = currentDocText.trim().replace(/\s+/g, ' ');
const normalizedOriginal = originalDocumentText.trim().replace(/\s+/g, ' ');

if (normalizedCurrent !== normalizedOriginal) {
throw new Error(Mismatch: Expected ${normalizedOriginal.length}, Got ${normalizedCurrent.length});
}---

Pitfall 5: OOXML Save Timing Issues

Problem:

  • Saving OOXML before accepting revisions means OOXML contains tracked changes
  • Saving OOXML after test runs means OOXML contains test changes
  • Need to save at the right time (after initial document capture, before any tests)

Symptoms:

  • OOXML contains wrong content
  • Restored document doesn't match original
  • Validation fails consistently

Solution:

  1. Save OOXML immediately after capturing original document - before any test runs
  2. Accept revisions before saving - ensures clean OOXML
  3. Store in global variable - originalDocumentOOXML persists across test runs
  4. Validate OOXML was saved - check length and log confirmation

Code:
// Save OOXML right after capturing original document
originalDocumentText = await wordAdapter.getWholeDocumentText();
originalDocumentOOXML = await wordAdapter.saveDocumentStateAsOOXML();
// Now originalDocumentOOXML contains clean state---

Pitfall 6: Range Expansion Issues (Reverted)

Problem:

  • When anchor text was shorter than origText, we tried to expand the range
  • Expansion logic was too aggressive and expanded beyond document bounds
  • Caused text duplication and document corruption

Symptoms:

  • Document length increased unexpectedly
  • Text appeared duplicated
  • Final document much longer than expected

Solution:

  • Reverted the expansion fix - simpler is better
  • Use exact anchor matching - if anchor found, use it as-is
  • Accept that some operations may need exact text match - better than corruption

Lesson: Sometimes reverting a "fix" is the right solution if it causes more problems.


Pitfall 7: Clean Copy Extraction Method Confusion

Problem:

  • Initially used body.text which includes tracked changes markup
  • Needed to use getReviewedText() to get clean copy
  • Method availability varies by Word API version

Symptoms:

  • Clean copy still showed tracked changes
  • Comparison failed because text included markup
  • Different results on different Word versions

Solution:

  1. Use getReviewedText(Word.ChangeTrackingVersion.current) - returns clean text
  2. Fallback to regular text property - if getReviewedText not available
  3. Test on target Word version - ensure method is available

Code:
try {
const reviewedTextResult = bodyRange.getReviewedText(Word.ChangeTrackingVersion.current);
await context.sync();
cleanText = reviewedTextResult.value; // Clean copy
} catch (reviewError) {
// Fallback
bodyRange.load('text');
await context.sync();
cleanText = bodyRange.text; // May include tracked changes
}---

Pitfall 8: Document State Not Persisting Across Test Runs

Problem:

  • Document state lost between test runs
  • Original document text not captured correctly
  • OOXML not saved or lost

Symptoms:

  • Each test run starts with wrong document state
  • Validation fails because document doesn't match original
  • Need to manually reset document

Solution:

  1. Capture original document on first run - store in originalDocumentText
  2. Save OOXML immediately - store in originalDocumentOOXML
  3. Use localStorage for persistence - survive page reloads
  4. Validate before each test - ensure document matches original

Code:
// Capture and persist
if (!originalDocumentText) {
originalDocumentText = await wordAdapter.getWholeDocumentText();
localStorage.setItem('e2e.originalDocumentText', originalDocumentText);
originalDocumentOOXML = await wordAdapter.saveDocumentStateAsOOXML();
}---

Pitfall 9: Insufficient Error Logging

Problem:

  • Errors occurred but weren't logged with enough detail
  • Hard to debug why restoration failed
  • No visibility into what was happening

Symptoms:

  • Restoration fails silently
  • No error messages in logs
  • Can't determine root cause

Solution:

  1. Comprehensive structured logging - log every step with metadata
  2. Error details included - error codes, names, messages, stack traces
  3. Log to dev server - accessible for analysis
  4. Log success AND failure - know what worked and what didn't

Code:
logger.structured('error', 'OOXML Restore Failed', {
error: error.message,
errorCode: error.code,
errorName: error.name,
stack: error.stack,
timestamp: new Date().toISOString()
});---

Pitfall 10: Validation Happening Too Early

Problem:

  • Validating document text before restoration completes
  • Validating against wrong reference text
  • Validation passes but document still has issues

Symptoms:

  • Validation passes but document still has tracked changes
  • Validation fails even though document looks correct
  • Inconsistent validation results

Solution:

  1. Validate AFTER restoration completes - ensure restoration finished
  2. Validate BEFORE LLM call - ensure document matches before sending
  3. Use same normalization - consistent comparison method
  4. Multiple validation points - catch issues at different stages

Code:ascript
// Step 1: Restore document
await wordAdapter.restoreDocumentStateFromOOXML(originalDocumentOOXML);

// Step 2: Validate after restoration
await validateOriginalTestClause();

// Step 3: Validate before LLM call
const docTextBeforeLLM = await wordAdapter.getWholeDocumentText();
// Compare with originalDocumentText---

Summary of Solutions

Pitfall Root Cause Solution
Tracked changes in OOXML OOXML contains markup Accept revisions before save, extract clean text after restore
acceptAllRevisions() fails API limitations Use getReviewedText() instead
Track changes mode active Mode not disabled Explicitly disable after restoration
Validation failures Whitespace/normalization Normalize before comparison
Wrong OOXML saved Timing issues Save immediately after capturing original
Range expansion bugs Over-aggressive expansion Revert to simpler approach
Clean copy confusion Wrong method used Use getReviewedText() for clean copy
State not persisting Not saved properly Use localStorage + global variables
Insufficient logging Missing error details Comprehensive structured logging
Validation timing Too early/late Validate at right points in flow

Key Lessons Learned

  1. OOXML is not magic - it preserves what you save, including tracked changes markup
  2. getReviewedText() is essential - the only reliable way to get clean text
  3. Multiple validation points - catch issues early and often
  4. Comprehensive logging - essential for debugging complex issues
  5. Fallback strategies - always have a backup plan
  6. Test incrementally - verify each step works before moving to next
  7. Sometimes simpler is better - reverting complex fixes can be the right choic