test(e2e): wire format integration test with TypeScript client#87
test(e2e): wire format integration test with TypeScript client#87
Conversation
Validates the complete wire format path: Go server generates tree → JSON over WebSocket → TypeScript client applies to DOM. First real consumer of WSMessageLogger infrastructure. Tests 7 wire format scenarios: initial render statics, update statics omission, range append/insert, range remove, range update, range reorder, and conditional toggle. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Code Review: Wire Format Integration TestI've reviewed PR #87 and here's my feedback: ✅ StrengthsExcellent Test Design:
Code Quality:
Test Coverage:
🔍 Potential Issues1. Race Condition in
|
There was a problem hiding this comment.
Pull request overview
Adds a new browser E2E test that exercises LiveTemplate’s end-to-end “wire format” update path (Go server → WS JSON → TS client → DOM), intended to validate statics behavior and range-diff operations across several scenarios.
Changes:
- Introduces
TestWireFormatcovering initial render + 6 incremental update scenarios (scalar, range ops, conditional toggle). - Adds local helper functions to inspect received WebSocket payloads for statics/range operation markers.
- Integrates existing Docker Chrome + client library serving utilities to run the test in CI-like conditions.
e2e/wire_format_test.go
Outdated
| // waitForNewTreeMessage waits for a new received WS message with a "tree" field | ||
| // after the given count of previously observed tree messages. | ||
| func waitForNewTreeMessage(wsLogger *e2etest.WSMessageLogger, prevCount int, timeout time.Duration) (map[string]interface{}, error) { | ||
| deadline := time.Now().Add(timeout) | ||
| for time.Now().Before(deadline) { | ||
| msgs := getReceivedMessagesWithTree(wsLogger) | ||
| if len(msgs) > prevCount { | ||
| return msgs[prevCount].Parsed, nil | ||
| } | ||
| time.Sleep(100 * time.Millisecond) | ||
| } | ||
| return nil, fmt.Errorf("timeout waiting for new tree message (had %d, still %d)", prevCount, len(getReceivedMessagesWithTree(wsLogger))) | ||
| } | ||
|
|
There was a problem hiding this comment.
waitForNewTreeMessage is defined but never used in this test file, which adds dead code and makes the test harder to maintain. Consider removing it, or using it consistently (e.g., to wait for the next tree message after each UI action).
| // waitForNewTreeMessage waits for a new received WS message with a "tree" field | |
| // after the given count of previously observed tree messages. | |
| func waitForNewTreeMessage(wsLogger *e2etest.WSMessageLogger, prevCount int, timeout time.Duration) (map[string]interface{}, error) { | |
| deadline := time.Now().Add(timeout) | |
| for time.Now().Before(deadline) { | |
| msgs := getReceivedMessagesWithTree(wsLogger) | |
| if len(msgs) > prevCount { | |
| return msgs[prevCount].Parsed, nil | |
| } | |
| time.Sleep(100 * time.Millisecond) | |
| } | |
| return nil, fmt.Errorf("timeout waiting for new tree message (had %d, still %d)", prevCount, len(getReceivedMessagesWithTree(wsLogger))) | |
| } |
e2e/wire_format_test.go
Outdated
| func waitForActionResponse(wsLogger *e2etest.WSMessageLogger, action string, timeout time.Duration) (map[string]interface{}, error) { | ||
| deadline := time.Now().Add(timeout) | ||
| for time.Now().Before(deadline) { | ||
| msgs := wsLogger.GetReceived() | ||
| for _, msg := range msgs { |
There was a problem hiding this comment.
waitForActionResponse scans all received messages and returns the first one whose meta.action matches. If multiple messages exist for the same action (e.g., reconnect/resend, retries, or multiple clicks), this can return a stale earlier response and make the assertions flaky. Consider tracking the current received-message count (or a since timestamp) before triggering the click and only matching messages observed after that point.
| func waitForActionResponse(wsLogger *e2etest.WSMessageLogger, action string, timeout time.Duration) (map[string]interface{}, error) { | |
| deadline := time.Now().Add(timeout) | |
| for time.Now().Before(deadline) { | |
| msgs := wsLogger.GetReceived() | |
| for _, msg := range msgs { | |
| // It only considers messages received after the function starts, to avoid matching | |
| // stale responses from previous actions. | |
| func waitForActionResponse(wsLogger *e2etest.WSMessageLogger, action string, timeout time.Duration) (map[string]interface{}, error) { | |
| // Snapshot the current number of received messages so we only look at newer ones. | |
| initial := wsLogger.GetReceived() | |
| startIndex := len(initial) | |
| deadline := time.Now().Add(timeout) | |
| for time.Now().Before(deadline) { | |
| msgs := wsLogger.GetReceived() | |
| if startIndex > len(msgs) { | |
| startIndex = len(msgs) | |
| } | |
| for _, msg := range msgs[startIndex:] { |
e2e/wire_format_test.go
Outdated
| // Listen for console errors | ||
| chromedp.ListenTarget(ctx, func(ev interface{}) { | ||
| if ev, ok := ev.(*runtime.EventConsoleAPICalled); ok { | ||
| for _, arg := range ev.Args { | ||
| log.Printf("Console: %s", string(arg.Value)) | ||
| } | ||
| } | ||
| }) |
There was a problem hiding this comment.
This chromedp.ListenTarget console hook logs every console call via log.Printf even when the test passes, but you already capture console output with consoleLogger and print it on failure. Keeping both is redundant and can produce very noisy CI logs; consider removing this listener (or limiting it to exceptions only) and relying on consoleLogger.Print() in the failure defer.
- Remove unused waitForNewTreeMessage function (dead code) - Fix waitForActionResponse to accept startFrom parameter, preventing stale message matches from previous actions - Remove redundant chromedp.ListenTarget console hook (already captured by ConsoleLogger) - Search all historical messages for batched remove_item operations instead of using forward-looking waitForActionResponse Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Code Review: Wire Format Integration TestOverviewThis PR adds a comprehensive e2e test that validates the complete wire format path from Go server tree generation through WebSocket to TypeScript client parsing and DOM updates. This is excellent coverage for a critical part of the LiveTemplate system. ✅ Strengths
🔍 Code Quality ObservationsPositive
Areas for Improvement1. Test Fragility - Batched Operations (e2e/wire_format_test.go:478-506) The comment in subtest 5 acknowledges that the diff engine may batch operations: // The update_item response may contain the ["u", ...] operation,
// OR the tree may be empty {} if the diff engine already batched
// the update into a prior response (e.g., remove_item).This is a code smell. The test is adapting to non-deterministic behavior, searching historical messages from
Recommendation: Either:
2. Magic Numbers for Timeouts Multiple 5-second timeouts scattered throughout: waitForActionResponse(wsLogger, "increment", msgCount, 5*time.Second)Recommendation: Extract to a constant: const actionResponseTimeout = 5 * time.Second3. Sequential Message Scanning The Minor optimization: Consider indexing messages by action name if this becomes a bottleneck. 4. Error Handling in Deferred Cleanup defer func() {
shutdownCtx, cancel := context.WithTimeout(context.Background(), 5*time.Second)
defer cancel()
if err := server.Shutdown(shutdownCtx); err != nil {
t.Logf("Server shutdown warning: %v", err) // Only logs, doesn't fail
}
}()This is fine for cleanup, but be aware that server shutdown errors are silently swallowed. 5. Template String Formatting The template string is clean and readable. Good use of explicit IDs for testing. 🐛 Potential Issues1. Race Condition Risk (Low)
Mitigation: The current implementation with 2. Test Data Coupling
Recommendation: This is acceptable for the current scope. Future tests could add edge cases. 3. No Verification of Wire Format Schema The test checks for presence of operations (
Recommendation: This is beyond scope for this PR, but consider schema validation tests in the future. 🔒 Security Considerations✅ No security issues identified. The test:
⚡ Performance Considerations
📋 Test CoverageWhat's Covered: ✅
What's Missing: ℹ️
Recommendation: These are nice-to-haves, not blockers. File follow-up issues if desired. 📝 Documentation & MaintainabilityExcellent:
Minor improvement:
🎯 Recommendations SummaryMust Fix: None - code is production-ready Should Consider:
Nice to Have:
✅ ApprovalThis is excellent work. The test is comprehensive, well-structured, and provides valuable coverage for wire format integration. The second commit addressing PR review comments shows good iteration. Status: ✅ Approved with minor suggestions The code follows Go best practices, integrates well with existing patterns, and the test infrastructure changes in the second commit ( Great job! 🎉 |
Summary
TestWireFormate2e test that validates the complete wire format path: Go server tree generation → JSON over WebSocket → TypeScript client parsing → DOM updateWSMessageLoggerinfrastructure (built in refactor(testing): add Docker Chrome helpers for reliable CI testing #85 but never used in a test)Test Scenarios
"s"(statics) keys["a", ...]operation["r", "item-id"]operation["u", "item-id", {...}]operation["o", [...ids]]operationTest plan
-count=3verified)SetupDockerChrome,ServeClientLibrary)Closes #96
🤖 Generated with Claude Code