Y
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
- Save OOXML from clean document
- Accept all revisions first
- Disable track changes mode
- Save OOXML (represents clean state)
- Restore OOXML
- Insert OOXML back into document
- May still contain tracked changes markup
- Extract clean text (key step)
- Use getReviewedText(Word.ChangeTrackingVersion.current)
- Returns text with all tracked changes applied
- This is the clean version
- 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:
- Accept revisions BEFORE saving OOXML - ensures OOXML is saved from clean state
- Extract clean text AFTER restore - use
getReviewedText()to get text with all changes applied - 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:
- Wrap in try-catch with detailed logging - capture error details
- Don't rely solely on
acceptAllRevisions()- use clean text extraction instead - 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:
- Disable track changes mode explicitly - use
changeTrackingMode = Word.ChangeTrackingMode.off - Fallback to
trackRevisions = false- if ChangeTrackingMode not available - 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:
- Normalize whitespace for comparison - use
.trim().replace(/\s+/g, ' ') - Validate at multiple points - after restore AND before LLM call
- Use
getReviewedText()for clean copy - ensures we're comparing clean text - 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:
- Save OOXML immediately after capturing original document - before any test runs
- Accept revisions before saving - ensures clean OOXML
- Store in global variable -
originalDocumentOOXMLpersists across test runs - 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.textwhich 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:
- Use
getReviewedText(Word.ChangeTrackingVersion.current)- returns clean text - Fallback to regular
textproperty - if getReviewedText not available - 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:
- Capture original document on first run - store in
originalDocumentText - Save OOXML immediately - store in
originalDocumentOOXML - Use localStorage for persistence - survive page reloads
- 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:
- Comprehensive structured logging - log every step with metadata
- Error details included - error codes, names, messages, stack traces
- Log to dev server - accessible for analysis
- 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:
- Validate AFTER restoration completes - ensure restoration finished
- Validate BEFORE LLM call - ensure document matches before sending
- Use same normalization - consistent comparison method
- 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
- OOXML is not magic - it preserves what you save, including tracked changes markup
getReviewedText()is essential - the only reliable way to get clean text- Multiple validation points - catch issues early and often
- Comprehensive logging - essential for debugging complex issues
- Fallback strategies - always have a backup plan
- Test incrementally - verify each step works before moving to next
- Sometimes simpler is better - reverting complex fixes can be the right choic