Skip to content

Conversation

@AbanoubGhadban
Copy link
Collaborator

@AbanoubGhadban AbanoubGhadban commented Oct 20, 2025

Summary

Remove this paragraph and provide a general description of the code changes in your pull
request... were there any bugs you had fixed? If so, mention them. If
these bugs have open GitHub issues, be sure to tag them here as well,
to keep the conversation linked together.

Pull Request checklist

Remove this line after checking all the items here. If the item is not applicable to the PR, both check it out and wrap it by ~.

  • Add/update test to cover these changes
  • Update documentation
  • Update CHANGELOG file

Add the CHANGELOG entry at the top of the file.

Other Information

Remove this paragraph and mention any other important and relevant information such as benchmarks.


This change is Reviewable

Summary by CodeRabbit

  • New Features

    • Added a Redis Receiver streaming demo component and server endpoints to showcase server-driven incremental rendering with optional async client parts.
  • Tests

    • Added Playwright end-to-end suite and ARIA snapshots to validate staged streaming, hydration, and interaction scenarios; added e2e test script.
  • Chores

    • CI updated to run the new e2e job; lint/TypeScript configs adjusted to include e2e files and tests; some legacy streaming tests removed.

@coderabbitai
Copy link
Contributor

coderabbitai bot commented Oct 20, 2025

Walkthrough

Adds Playwright-based E2E tests and Redis-backed test fixtures for the dummy app, a RedisReceiver streaming component with Rails endpoints/routes/views, Playwright config and snapshots, CI job to run tests, ESLint/TS adjustments, and removal of legacy streaming system tests.

Changes

Cohort / File(s) Summary
CI
\.circleci/config.yml
Added dummy-app-node-renderer-e2-tests job and wired it into build-and-test workflow; stores Capybara/Playwright artifacts.
Playwright tooling
react_on_rails_pro/spec/dummy/playwright.config.ts, react_on_rails_pro/spec/dummy/package.json, react_on_rails_pro/spec/dummy/.gitignore
Added Playwright config, devDeps (@playwright/test, @types/node), e2e-test script, and Playwright-related gitignore entries.
E2E fixtures & tests
react_on_rails_pro/spec/dummy/e2e-tests/fixture.ts, react_on_rails_pro/spec/dummy/e2e-tests/streaming.spec.ts
New Redis-controlled Playwright fixtures, controllers, helpers and streaming E2E test suite with staged snapshot assertions and network/console assertions.
RedisReceiver feature
react_on_rails_pro/spec/dummy/client/app/ror-auto-load-components/RedisReceiver.jsx, react_on_rails_pro/spec/dummy/app/controllers/pages_controller.rb, react_on_rails_pro/spec/dummy/app/views/pages/redis_receiver.html.erb, react_on_rails_pro/spec/dummy/config/routes.rb, react_on_rails_pro/spec/dummy/client/app/utils/redisReceiver.ts
Added RedisReceiver React component (async items, Suspense, ErrorBoundary), Rails controller actions for stream creation/testing, view template, routes, and extracted REDIS_READ_TIMEOUT constant.
Router & UI
react_on_rails_pro/spec/dummy/client/app/components/ServerComponentRouter.tsx, react_on_rails_pro/spec/dummy/app/views/pages/server_router.html.erb, react_on_rails_pro/spec/dummy/client/app/components/RSCPostsPage/ToggleContainer.jsx
Added navigation/route for RedisReceiver, included requestId prop in server_router props, and added className="toggle-button" to ToggleContainer button.
Client directive
packages/react-on-rails-pro/src/RSCRoute.tsx
Added "use client" directive at top of file.
Snapshots
react_on_rails_pro/spec/dummy/__snapshots__/streaming.spec.ts/stage{0..5}.aria.yml
Added six ARIA snapshot files representing progressive streaming stages.
ESLint & TypeScript
react_on_rails_pro/eslint.config.mjs, react_on_rails_pro/spec/dummy/tsconfig.json
Added ESLint overrides for spec/dummy/e2e-tests/* and expanded tsconfig include to cover client sources, e2e-tests, and Playwright config.
Test cleanup
react_on_rails_pro/spec/dummy/spec/system/integration_spec.rb
Removed extensive legacy streaming/hydration/RSC payload system tests and related helpers.
Package scripts
react_on_rails_pro/package-scripts.yml
Adjusted TypeScript check path to use ./tsconfig.json under spec/dummy.

Sequence Diagram(s)

sequenceDiagram
    participant PW as Playwright Test
    participant Browser as Browser
    participant Rails as Rails Server
    participant Redis as Redis
    participant Component as RedisReceiver

    PW->>+Rails: GET /redis_receiver?request_id=RID
    Rails->>+Redis: create stream:RID (background thread)
    Rails-->>-Browser: SSR HTML (fallbacks) + streaming endpoint
    Browser->>Component: mount with requestId
    Component->>Redis: listenToRequestData(requestId)
    loop items 1..5
      Redis-->>Component: send ItemN
      Component-->>Browser: stream incremental update
      PW->>Browser: capture snapshot stage N
    end
    PW->>Redis: cleanup stream:RID
Loading

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~45 minutes

Possibly related issues

Possibly related PRs

Suggested reviewers

  • justin808
  • alexeyr-ci

Poem

🐰 Hoped into streams with a twitchy nose,

I fed Redis values in five little rows.
Playwright blinked, snapshots all neat —
CI now runs tests with a rhythmic beat.
Hooray for green builds and flaky-test defeat!

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 pull request title "Replace streaming flaky tests with Playwright E2E tests" accurately and specifically describes the main change in the changeset. The summary confirms that large blocks of streaming, hydration, and RSC test examples are being removed from the existing Capybara-based test suite in integration_spec.rb, while new Playwright end-to-end tests are being introduced via streaming.spec.ts along with supporting fixtures and configuration. The title clearly conveys both the removal of the old testing approach and the adoption of the new one, making it easy for developers scanning the history to understand the primary purpose of this change.
Docstring Coverage ✅ Passed No functions found in the changes. Docstring coverage check skipped.
✨ Finishing touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Post copyable unit tests in a comment
  • Commit unit tests in branch abanoubghadban/ror1866/fix-streaming-flaky-tests

📜 Recent review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 7176d49 and f5de26b.

📒 Files selected for processing (1)
  • react_on_rails_pro/package-scripts.yml (1 hunks)
🧰 Additional context used
📓 Path-based instructions (1)
**/*.{js,jsx,ts,tsx,css,scss,json,yml,yaml,md}

📄 CodeRabbit inference engine (CLAUDE.md)

Prettier is the sole authority for formatting all non-Ruby files; never manually format them

Files:

  • react_on_rails_pro/package-scripts.yml
⏰ 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). (8)
  • GitHub Check: dummy-app-integration-tests (3.2, 20)
  • GitHub Check: dummy-app-integration-tests (3.4, 22)
  • GitHub Check: rspec-package-tests (3.4, latest)
  • GitHub Check: rspec-package-tests (3.4, minimum)
  • GitHub Check: rspec-package-tests (3.2, latest)
  • GitHub Check: rspec-package-tests (3.2, minimum)
  • GitHub Check: build
  • GitHub Check: build-and-test
🔇 Additional comments (1)
react_on_rails_pro/package-scripts.yml (1)

18-18: tsconfig.json path change is correct and verified.

The file react_on_rails_pro/spec/dummy/tsconfig.json exists and has been properly configured with expanded include patterns for client sources (client/**/*.ts, client/**/*.tsx, client/**/*.d.ts), E2E tests (e2e-tests/*), and Playwright config (./playwright.config.ts). The path change from client/tsconfig.json to ./tsconfig.json correctly targets this consolidated root configuration file.


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

@claude
Copy link

claude bot commented Oct 20, 2025

Pull Request Review: Fix streaming flaky tests

Thank you for working on fixing the flaky streaming tests! This is a valuable improvement to the test infrastructure. Below is my detailed review:

🎯 Summary

This PR migrates flaky Capybara-based streaming tests to Playwright with controlled Redis streaming, improving test reliability and coverage. The changes include:

  • New Playwright E2E test infrastructure with Redis fixtures
  • New RedisReceiver component for testing incremental rendering
  • Removal of 421 lines of flaky RSpec system tests
  • CI/CD pipeline updates to run Playwright tests

✅ Strengths

  1. Well-structured test fixtures: The Playwright fixtures in fixture.ts are well-designed with clear separation of concerns and good use of fixture composition
  2. Snapshot testing strategy: Using ARIA snapshots for incremental rendering is clever and reduces assertion complexity
  3. Comprehensive test coverage: Tests cover multiple scenarios (direct access, router navigation, async client components)
  4. Better test control: Redis-controlled tests provide deterministic behavior vs. time-based flaky tests

🐛 Potential Issues

Critical Issues

  1. Missing PR checklist items (react_on_rails_pro/spec/dummy/app/controllers/pages_controller.rb:97)

    raise "request_id is required at the url" if @request_id.empty?
    • This will fail when request_id is nil (calling .empty? on nil raises NoMethodError)
    • Fix: Use if @request_id.blank? or if @request_id.nil? || @request_id.empty?
  2. HTML attribute error (react_on_rails_pro/spec/dummy/client/app/ror-auto-load-components/RedisReceiver.jsx:12)

    <section class={`redis-item${itemIndex}-container`}>
    • Should be className not class in React/JSX
    • This will cause a warning and may not apply styles correctly
  3. Missing "use client" directive (packages/react-on-rails-pro/src/RSCRoute.tsx:1)

    • Added "use client" directive - good catch!
    • However, verify this doesn't break server-side usage of this component

Code Quality Issues

  1. Test files should be cleaned up (react_on_rails_pro/spec/dummy/e2e-tests/dummy.spec.ts)

    • This appears to be a test/example file that should be removed before merging
    • File contains commented-out code and dummy tests
  2. Typo in filename (react_on_rails_pro/spec/dummy/e2e-tests/dummt-fixture.ts)

    • Filename has typo: dummt-fixture.ts should probably be dummy-fixture.ts
    • This file also appears to be for testing/exploration and should likely be removed
  3. Missing await (react_on_rails_pro/spec/dummy/e2e-tests/streaming.spec.ts:25, 55)

    sendRedisItemValue(0, 'Incremental Value1');
    await matchPageSnapshot('stage1');
    • sendRedisItemValue returns a Promise but is not awaited
    • This could cause race conditions where snapshot is taken before Redis value is sent
    • Fix: Add await before all sendRedisItemValue calls
  4. Missing await in waitForConsoleMessage (react_on_rails_pro/spec/dummy/e2e-tests/streaming.spec.ts:42)

    waitForConsoleMessage('ToggleContainer with title');
    • Missing await - this promise won't be waited on
    • Fix: await waitForConsoleMessage('ToggleContainer with title');
  5. Inconsistent Number type (react_on_rails_pro/spec/dummy/e2e-tests/fixture.ts:20)

    sendRedisItemValue: (itemIndex: Number, value: unknown) => Promise<void>;
    • Should use lowercase number not Number (TypeScript primitive vs. wrapper object)
    • Fix: itemIndex: number

⚠️ Performance Considerations

  1. Redis connection pooling: Each worker creates its own Redis client (fixture.ts:27-34), which is good for worker-scoped isolation
  2. Stream cleanup: The code properly cleans up Redis streams by deleting processed messages, preventing memory leaks
  3. Timeout handling: 8-second timeout for getValue is reasonable but might need adjustment based on CI performance

🔒 Security Concerns

  1. Redis URL from environment: Using process.env.REDIS_URL is good practice
  2. Request ID generation: Using randomUUID() for request IDs is secure
  3. No sensitive data exposure: Tests don't appear to expose sensitive information

🧪 Test Coverage Issues

  1. Missing error scenarios: Tests focus on happy path - consider adding:

    • Tests for timeout scenarios
    • Tests for Redis connection failures
    • Tests for malformed data in Redis streams
  2. Missing documentation: The complex test fixtures would benefit from JSDoc comments explaining:

    • The purpose of each fixture
    • How to use them in new tests
    • The relationship between fixtures

📋 Missing Checklist Items

The PR description shows the checklist was not completed:

  • Add/update test to cover these changes ✅ (Done)
  • Update documentation ❌ (Missing - should update docs about running E2E tests)
  • Update CHANGELOG file ❌ (Missing - should add entry about test improvements)

🔧 Recommended Changes

High Priority:

  1. Fix @request_id.empty? to handle nil case
  2. Change class to className in RedisReceiver.jsx
  3. Add await to all sendRedisItemValue calls in tests
  4. Add await to waitForConsoleMessage call
  5. Remove dummy.spec.ts and dummt-fixture.ts files
  6. Fix TypeScript type: Numbernumber

Medium Priority:
7. Add CHANGELOG entry
8. Update documentation for running Playwright tests
9. Add error scenario tests
10. Add JSDoc comments to test fixtures

Low Priority:
11. Consider adding test cleanup hooks to ensure Redis streams are deleted
12. Consider adding retry logic for flaky network conditions


📊 Overall Assessment

Code Quality: 7/10

  • Well-structured but has several fixable bugs
  • Good test architecture but needs cleanup

Best Practices: 8/10

  • Follows Playwright best practices
  • Good use of fixtures and snapshots
  • Missing some documentation

Completeness: 6/10

  • Core functionality complete
  • Missing documentation updates
  • Missing CHANGELOG entry
  • Has leftover test files

✅ Recommendation

Request Changes - This PR is heading in the right direction but needs the critical bug fixes before merging. Once the issues above are addressed, this will be a solid improvement to the test suite.

Great work on tackling the flaky tests! The migration to Playwright with controlled Redis streaming is a smart approach. 🚀

@coderabbitai coderabbitai bot added the bug label Oct 20, 2025
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: 11

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (1)
packages/react-on-rails-pro/src/RSCRoute.tsx (1)

78-81: Correct the reference directive from react/experimental to react.

The CI failures stem from an incorrect reference directive path. The correct triple‑slash directive should be /// <reference types="react" />, not react/experimental. React.use is defined in @types/react v18.3.18 with proper function overloads, and your installed versions are correct. The root tsconfig already includes "typeRoots": ["./node_modules/@types"], so the directive can be omitted entirely or corrected to:

-/// <reference types="react/experimental" />
+/// <reference types="react" />

Alternatively, remove the reference directive entirely since your tsconfig automatically resolves @types. Either approach resolves the TypeScript compilation errors.

🧹 Nitpick comments (9)
react_on_rails_pro/spec/dummy/e2e-tests/dummy.spec.ts (2)

1-12: Consider removing commented code.

The commented-out test code spanning lines 1-12 should either be removed or uncommented if it's needed. Keeping large blocks of commented code reduces maintainability.


19-21: Remove console.log from test code.

The test contains a console.log statement (line 20) that appears to be for debugging. Consider removing it or converting it to a proper assertion if this is production test code. Also, line 21 is missing a semicolon for consistency.

Apply this diff if this is production test code:

 test('eee', ({ h2 }) => {
-  console.log('TEst', h2);
-})
+  // Add proper assertions here
+  expect(h2).toBeDefined();
+});

Or remove the file entirely if it's just a temporary fixture test.

react_on_rails_pro/spec/dummy/e2e-tests/streaming.spec.ts (1)

16-21: Consider extracting test configuration to improve maintainability.

The test configuration array uses as const and forEach to generate multiple test cases. While this works, consider extracting it to a separate constant for better readability and reusability.

+const TEST_CONFIGURATIONS = [
+  ['RedisReceiver', redisReceiverPageTest],
+  ['RedisReceiver inside router page', redisReceiverInsideRouterPageTest],
+  ['RedisReceiver inside router after navigation', redisReceiverPageAfterNavigationTest],
+  ['RedisReceiver with Async Toggle Container Client Component', redisReceiverPageWithAsyncClientComponentTest],
+] as const;
+
-([
-  ['RedisReceiver', redisReceiverPageTest],
-  ['RedisReceiver inside router page', redisReceiverInsideRouterPageTest],
-  ['RedisReceiver inside router after navigation', redisReceiverPageAfterNavigationTest],
-  ['RedisReceiver with Async Toggle Container Client Component', redisReceiverPageWithAsyncClientComponentTest],
-] as const).forEach(([pageName, test]) => {
+TEST_CONFIGURATIONS.forEach(([pageName, test]) => {
react_on_rails_pro/spec/dummy/app/controllers/pages_controller.rb (1)

89-93: Don’t raise after streaming response; return an HTTP status instead

Raising after the stream may cause noisy 500s. Prefer an explicit status.

-    Rails.logger.error "Redis thread timed out"
-    raise "Redis thread timed out"
+    Rails.logger.error "Redis thread timed out"
+    head :gateway_timeout
react_on_rails_pro/spec/dummy/e2e-tests/dummt-fixture.ts (2)

12-16: Fix “Unexpected empty object pattern” in fixtures

Replace empty destructuring with an unused arg to satisfy linters.

-  h: [async({}, use) => {
+  h: [async (_args, use) => {
     console.log('F1')
     await use('F1');
     console.log('F1 end');
   }, { auto: true }]
-  h: [async({}, use) => {
+  h: [async (_args, use) => {
     console.log('Fm')
     await use('Fm');
     console.log('Fm end');
   }, { auto: true }]

Also applies to: 20-24


1-1: Optional: file name typo

Consider renaming dummt-fixture.ts to dummy-fixture.ts for clarity.

react_on_rails_pro/spec/dummy/client/app/ror-auto-load-components/RedisReceiver.jsx (2)

21-24: Prefer JSX children over a children prop

Pass children via JSX to satisfy linters and React conventions.

-const AsyncToggleContainer = async ({ children, childrenTitle, getValue }) => {
+const AsyncToggleContainer = async ({ children, childrenTitle, getValue }) => {
   await getValue('ToggleContainer');
-  return <ToggleContainer children={children} childrenTitle={childrenTitle} />
+  return <ToggleContainer childrenTitle={childrenTitle}>{children}</ToggleContainer>
 }

41-47: Avoid passing unused props to ToggleContainer

When asyncToggleContainer is false, getValue is unnecessary. Keep prop surface minimal.

-  const toggleContainerGetValueParam = asyncToggleContainer ? getValue : undefined;
+  const toggleContainerGetValueParam = asyncToggleContainer ? getValue : undefined; // consider only spreading when defined
...
-  <UsedToggleContainer childrenTitle="Redis Items" getValue={toggleContainerGetValueParam}>
+  <UsedToggleContainer childrenTitle="Redis Items" {...(toggleContainerGetValueParam ? { getValue: toggleContainerGetValueParam } : {})}>
react_on_rails_pro/spec/dummy/e2e-tests/fixture.ts (1)

27-27: Fix “Unexpected empty object pattern” in fixtures

Replace empty destructuring with an unused arg name.

-  redisClient: [async ({}, use, workerInfo) => {
+  redisClient: [async (_args, use, workerInfo) => {
-  redisRequestId: async ({}, use) => {
+  redisRequestId: async (_args, use) => {

Also applies to: 36-36

📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 2b5bab8 and c5851aa.

⛔ Files ignored due to path filters (2)
  • react_on_rails_pro/spec/dummy/yarn.lock is excluded by !**/yarn.lock, !**/*.lock
  • react_on_rails_pro/yarn.lock is excluded by !**/yarn.lock, !**/*.lock
📒 Files selected for processing (23)
  • .circleci/config.yml (2 hunks)
  • packages/react-on-rails-pro/src/RSCRoute.tsx (1 hunks)
  • react_on_rails_pro/spec/dummy/.gitignore (1 hunks)
  • react_on_rails_pro/spec/dummy/__snapshots__/streaming.spec.ts/stage0.aria.yml (1 hunks)
  • react_on_rails_pro/spec/dummy/__snapshots__/streaming.spec.ts/stage1.aria.yml (1 hunks)
  • react_on_rails_pro/spec/dummy/__snapshots__/streaming.spec.ts/stage2.aria.yml (1 hunks)
  • react_on_rails_pro/spec/dummy/__snapshots__/streaming.spec.ts/stage3.aria.yml (1 hunks)
  • react_on_rails_pro/spec/dummy/__snapshots__/streaming.spec.ts/stage4.aria.yml (1 hunks)
  • react_on_rails_pro/spec/dummy/__snapshots__/streaming.spec.ts/stage5.aria.yml (1 hunks)
  • react_on_rails_pro/spec/dummy/app/controllers/pages_controller.rb (1 hunks)
  • react_on_rails_pro/spec/dummy/app/views/pages/redis_receiver.html.erb (1 hunks)
  • react_on_rails_pro/spec/dummy/app/views/pages/server_router.html.erb (1 hunks)
  • react_on_rails_pro/spec/dummy/client/app/components/RSCPostsPage/ToggleContainer.jsx (1 hunks)
  • react_on_rails_pro/spec/dummy/client/app/components/ServerComponentRouter.tsx (2 hunks)
  • react_on_rails_pro/spec/dummy/client/app/ror-auto-load-components/RedisReceiver.jsx (1 hunks)
  • react_on_rails_pro/spec/dummy/config/routes.rb (1 hunks)
  • react_on_rails_pro/spec/dummy/e2e-tests/dummt-fixture.ts (1 hunks)
  • react_on_rails_pro/spec/dummy/e2e-tests/dummy.spec.ts (1 hunks)
  • react_on_rails_pro/spec/dummy/e2e-tests/fixture.ts (1 hunks)
  • react_on_rails_pro/spec/dummy/e2e-tests/streaming.spec.ts (1 hunks)
  • react_on_rails_pro/spec/dummy/package.json (2 hunks)
  • react_on_rails_pro/spec/dummy/playwright.config.ts (1 hunks)
  • react_on_rails_pro/spec/dummy/spec/system/integration_spec.rb (0 hunks)
💤 Files with no reviewable changes (1)
  • react_on_rails_pro/spec/dummy/spec/system/integration_spec.rb
🧰 Additional context used
📓 Path-based instructions (3)
**/*.{js,jsx,ts,tsx,css,scss,json,yml,yaml,md}

📄 CodeRabbit inference engine (CLAUDE.md)

Prettier is the sole authority for formatting all non-Ruby files; never manually format them

Files:

  • react_on_rails_pro/spec/dummy/__snapshots__/streaming.spec.ts/stage2.aria.yml
  • react_on_rails_pro/spec/dummy/__snapshots__/streaming.spec.ts/stage5.aria.yml
  • react_on_rails_pro/spec/dummy/client/app/components/RSCPostsPage/ToggleContainer.jsx
  • react_on_rails_pro/spec/dummy/__snapshots__/streaming.spec.ts/stage1.aria.yml
  • packages/react-on-rails-pro/src/RSCRoute.tsx
  • react_on_rails_pro/spec/dummy/client/app/components/ServerComponentRouter.tsx
  • react_on_rails_pro/spec/dummy/package.json
  • react_on_rails_pro/spec/dummy/__snapshots__/streaming.spec.ts/stage3.aria.yml
  • react_on_rails_pro/spec/dummy/__snapshots__/streaming.spec.ts/stage4.aria.yml
  • react_on_rails_pro/spec/dummy/e2e-tests/dummt-fixture.ts
  • react_on_rails_pro/spec/dummy/playwright.config.ts
  • react_on_rails_pro/spec/dummy/__snapshots__/streaming.spec.ts/stage0.aria.yml
  • react_on_rails_pro/spec/dummy/e2e-tests/streaming.spec.ts
  • react_on_rails_pro/spec/dummy/e2e-tests/dummy.spec.ts
  • react_on_rails_pro/spec/dummy/e2e-tests/fixture.ts
  • react_on_rails_pro/spec/dummy/client/app/ror-auto-load-components/RedisReceiver.jsx
{Gemfile,Rakefile,config.ru,**/*.{rb,rake,gemspec,ru}}

📄 CodeRabbit inference engine (CLAUDE.md)

{Gemfile,Rakefile,config.ru,**/*.{rb,rake,gemspec,ru}}: All Ruby code must pass RuboCop with zero offenses before commit/push
RuboCop is the sole authority for Ruby file formatting; never manually format Ruby files

Files:

  • react_on_rails_pro/spec/dummy/app/controllers/pages_controller.rb
  • react_on_rails_pro/spec/dummy/config/routes.rb
**/*.{js,jsx,ts,tsx}

📄 CodeRabbit inference engine (CLAUDE.md)

Use ESLint for JS/TS code (lint via rake lint or yarn lint)

Files:

  • react_on_rails_pro/spec/dummy/client/app/components/RSCPostsPage/ToggleContainer.jsx
  • packages/react-on-rails-pro/src/RSCRoute.tsx
  • react_on_rails_pro/spec/dummy/client/app/components/ServerComponentRouter.tsx
  • react_on_rails_pro/spec/dummy/e2e-tests/dummt-fixture.ts
  • react_on_rails_pro/spec/dummy/playwright.config.ts
  • react_on_rails_pro/spec/dummy/e2e-tests/streaming.spec.ts
  • react_on_rails_pro/spec/dummy/e2e-tests/dummy.spec.ts
  • react_on_rails_pro/spec/dummy/e2e-tests/fixture.ts
  • react_on_rails_pro/spec/dummy/client/app/ror-auto-load-components/RedisReceiver.jsx
🧬 Code graph analysis (5)
react_on_rails_pro/spec/dummy/app/controllers/pages_controller.rb (1)
react_on_rails_pro/lib/react_on_rails_pro/concerns/stream.rb (1)
  • stream_view_containing_react_components (33-47)
react_on_rails_pro/spec/dummy/playwright.config.ts (1)
react_on_rails_pro/spec/dummy/client/node-renderer.js (1)
  • process (5-5)
react_on_rails_pro/spec/dummy/e2e-tests/dummy.spec.ts (1)
react_on_rails_pro/spec/dummy/e2e-tests/dummt-fixture.ts (1)
  • test2 (27-33)
react_on_rails_pro/spec/dummy/e2e-tests/fixture.ts (1)
react_on_rails_pro/spec/dummy/client/app/ror-auto-load-components/RedisReceiver.jsx (1)
  • value (7-7)
react_on_rails_pro/spec/dummy/client/app/ror-auto-load-components/RedisReceiver.jsx (2)
react_on_rails_pro/spec/dummy/client/app/components/RSCPostsPage/ToggleContainer.jsx (1)
  • ToggleContainer (3-19)
react_on_rails_pro/spec/dummy/client/app/components/ErrorBoundary.tsx (1)
  • ErrorBoundary (7-9)
🪛 Biome (2.1.2)
react_on_rails_pro/spec/dummy/e2e-tests/dummt-fixture.ts

[error] 12-12: Unexpected empty object pattern.

(lint/correctness/noEmptyPattern)


[error] 20-20: Unexpected empty object pattern.

(lint/correctness/noEmptyPattern)

react_on_rails_pro/spec/dummy/e2e-tests/fixture.ts

[error] 27-27: Unexpected empty object pattern.

(lint/correctness/noEmptyPattern)


[error] 36-36: Unexpected empty object pattern.

(lint/correctness/noEmptyPattern)

react_on_rails_pro/spec/dummy/client/app/ror-auto-load-components/RedisReceiver.jsx

[error] 23-23: Avoid passing children using a prop

The canonical way to pass children in React is to use JSX elements

(lint/correctness/noChildrenProp)

🪛 GitHub Actions: JS unit tests for Renderer package
packages/react-on-rails-pro/src/RSCRoute.tsx

[error] 80-80: TS2339: Property 'use' does not exist on type 'typeof React'.

🪛 GitHub Actions: Lint JS and Ruby
packages/react-on-rails-pro/src/RSCRoute.tsx

[error] 80-80: TypeScript error: Property 'use' does not exist on type 'typeof React'.

🪛 GitHub Actions: Main test
packages/react-on-rails-pro/src/RSCRoute.tsx

[error] 80-80: TS2339: Property 'use' does not exist on type 'typeof React'.

⏰ 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). (5)
  • GitHub Check: rspec-package-tests (3.4, latest)
  • GitHub Check: rspec-package-tests (3.2, latest)
  • GitHub Check: rspec-package-tests (3.4, minimum)
  • GitHub Check: rspec-package-tests (3.2, minimum)
  • GitHub Check: build-and-test
🔇 Additional comments (14)
react_on_rails_pro/spec/dummy/.gitignore (1)

3-9: LGTM! Playwright test artifacts properly ignored.

The Playwright-related ignore patterns are standard and complete, covering test results, reports, cache, and authentication state files. This aligns well with the PR's introduction of Playwright-based end-to-end testing infrastructure.

react_on_rails_pro/spec/dummy/client/app/components/RSCPostsPage/ToggleContainer.jsx (1)

13-13: LGTM! Test selector added.

The className addition provides a stable selector for e2e tests and aligns with the PR's test infrastructure improvements.

react_on_rails_pro/spec/dummy/__snapshots__/streaming.spec.ts/stage0.aria.yml (1)

1-9: ARIA snapshot test fixture approved.

This initial stage snapshot properly captures the loading state for streaming ARIA tests.

react_on_rails_pro/spec/dummy/__snapshots__/streaming.spec.ts/stage1.aria.yml (1)

1-9: ARIA snapshot shows correct incremental progression.

Stage1 properly captures the first item received while others remain pending.

react_on_rails_pro/spec/dummy/__snapshots__/streaming.spec.ts/stage2.aria.yml (1)

1-9: ARIA snapshot progression is realistic.

Stage2 correctly captures non-sequential item arrival, simulating real streaming scenarios.

react_on_rails_pro/spec/dummy/__snapshots__/streaming.spec.ts/stage3.aria.yml (1)

1-9: ARIA snapshot consistent with progression.

Stage3 properly advances item delivery while maintaining accessibility structure.

react_on_rails_pro/spec/dummy/__snapshots__/streaming.spec.ts/stage4.aria.yml (1)

1-9: ARIA snapshot validates near-complete streaming state.

Stage4 correctly represents most items received with final item pending.

react_on_rails_pro/spec/dummy/__snapshots__/streaming.spec.ts/stage5.aria.yml (1)

1-9: ARIA snapshot captures complete streaming terminal state.

Stage5 correctly represents all items successfully received from Redis.

.circleci/config.yml (2)

350-350: Verify Redis service image version.

Redis 6.2.6 was released in 2021. Consider whether you intentionally target 6.2 for compatibility testing or if upgrading to Redis 7.x or 8.x is appropriate. Verify this aligns with your production environment expectations.


423-426: Workflow integration looks correct.

The e2e test job is properly added to the workflow with correct dependency ordering.

react_on_rails_pro/spec/dummy/package.json (1)

100-100: Script addition properly supports e2e test workflow.

The "e2e-test" script command correctly maps to playwright test, enabling the CircleCI job to execute end-to-end tests.

react_on_rails_pro/spec/dummy/e2e-tests/streaming.spec.ts (1)

41-69: Verify early hydration test behavior.

The early hydration test (lines 41-69) performs UI interactions before completing incremental rendering. Ensure this accurately reflects the intended user behavior and that the waitForConsoleMessage on line 42 doesn't cause timing issues that could contribute to test flakiness.

Consider adding explicit wait conditions or timeouts to make the test more deterministic:

   test(`early hydration of page: ${pageName}`, async ({ page, waitForConsoleMessage, matchPageSnapshot, sendRedisItemValue }) => {
-    waitForConsoleMessage('ToggleContainer with title');
+    await waitForConsoleMessage('ToggleContainer with title');
 
     await page.click('.toggle-button');

Note: Verify if waitForConsoleMessage is intentionally not awaited or if this is a missing await.

react_on_rails_pro/spec/dummy/app/views/pages/redis_receiver.html.erb (1)

1-5: No action required. @request_id initialization verified.

The verification confirms that both controller actions properly initialize @request_id:

  • redis_receiver (line 73): Sets @request_id = SecureRandom.uuid
  • redis_receiver_for_testing (line 96): Sets @request_id = params[:request_id] with validation

The view dependency is satisfied.

react_on_rails_pro/spec/dummy/e2e-tests/fixture.ts (1)

49-54: Confirm stream field naming matches client expectations

You’re writing fields as : (colon-prefixed). Ensure listenToRequestData/getValue expects this shape.

If not required, drop the colon:

-      await redisClient.xAdd(`stream:${redisRequestId}`, '*', { [`:${key}`]: JSON.stringify(value) });
+      await redisClient.xAdd(`stream:${redisRequestId}`, '*', { [key]: JSON.stringify(value) });

Comment on lines 346 to 390
# TODO: DRY with previous job
dummy-app-node-renderer-e2-tests:
docker:
- image: *docker_image
- image: cimg/redis:6.2.6
steps:
- checkout
- run: *print-system-info
- restore_cache: *restore-package-gem-cache
- restore_cache: *restore-package-node-modules-cache
- restore_cache: *restore-dummy-app-node-modules-cache
- restore_cache: *restore-dummy-app-gem-cache
- run: rm -rf react_on_rails_pro/spec/dummy/public/webpack
- run: rm -rf react_on_rails_pro/spec/dummy/ssr-generated
- restore_cache: *restore-dummy-app-webpack-bundle-cache
- run: *install-dummy-app-ruby-gems
- run: *install-package-node-modules
- run: *install-latest-chrome
- run: *install-dummy-app-node-modules
- run:
name: Generate file-system based entrypoints (Pro)
working_directory: react_on_rails_pro
command: cd spec/dummy && bundle exec rake react_on_rails:generate_packs
- run:
name: Run Pro Node renderer in a background
working_directory: react_on_rails_pro
command: cd spec/dummy && yarn run node-renderer
background: true
- run:
name: run rails server in background (Pro dummy app)
working_directory: react_on_rails_pro
command: cd spec/dummy && RAILS_ENV=test rails server
background: true
- run:
name: wait for rails server to start
command: |
while ! curl -s http://localhost:3000 > /dev/null; do sleep 1; done
- run:
name: Run playwright tests (Pro dummy app)
working_directory: react_on_rails_pro/spec/dummy
command: yarn e2e-test
- store_test_results:
path: react_on_rails_pro/spec/dummy/results.xml
- store_artifacts:
path: react_on_rails_pro/spec/dummy/playwright-report
Copy link
Contributor

Choose a reason for hiding this comment

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

🛠️ Refactor suggestion | 🟠 Major

Refactor to eliminate job duplication via YAML anchors.

The dummy-app-node-renderer-e2-tests job is nearly identical to rspec-dummy-app-node-renderer (lines 289–344), with only the Redis service addition and test command differing. The TODO comment acknowledges this.

Use YAML anchors to extract common steps and compose the jobs to follow DRY principles:

- &e2e-test-steps
  - checkout
  - run: *print-system-info
  # ... (shared steps)
  - run:
      name: Run tests
      working_directory: react_on_rails_pro/spec/dummy
      command: << parameters.test_command >>

Then define both jobs by extending shared steps with job-specific parameters.

},
"devDependencies": {
"@babel/preset-typescript": "^7.23.2",
"@playwright/test": "^1.56.1",
Copy link
Contributor

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🔴 Critical

🧩 Analysis chain

Verify Playwright version is latest or intentional.

Playwright ^1.56.1 is relatively recent. Confirm this version is current and compatible with your Node.js/React environment as of October 2025.


🌐 Web query:

What is the latest version of Playwright test runner as of October 2025?

💡 Result:

As of October 20, 2025 the latest stable Playwright Test runner release is @playwright/test 1.56.0 (Playwright 1.56). [Oct 6, 2025]. [1][2]

Note: canary/alpha builds for 1.57.x are available (1.57.0-alpha) if you need the very latest pre-release changes. [2][3]

Sources:
[1] Playwright release notes — Version 1.56. (playwright.dev)
[2] npm/yarn release listings showing 1.56.0 (stable) and 1.57.0-alpha (canary). (classic.yarnpkg.com)

Citations:


Update Playwright version to available stable release.

The specified version ^1.56.1 references a patch version that doesn't exist in stable releases. As of October 20, 2025, the latest stable Playwright Test runner release is @playwright/test 1.56.0. Update to ^1.56.0 to match the actual latest stable release.

🤖 Prompt for AI Agents
In react_on_rails_pro/spec/dummy/package.json at line 80, the Playwright
dependency is pinned to ^1.56.1 which is not a valid stable release; change the
version specifier to ^1.56.0 so it matches the actual latest stable
@playwright/test release. Update the package.json entry to use "^1.56.0", run
npm/yarn install to update lockfile, and commit the updated package.json and
lockfile.

@claude
Copy link

claude bot commented Oct 20, 2025

PR Review: Fix streaming flaky tests

Summary

This PR replaces flaky RSpec system tests with Playwright-based end-to-end tests to verify React Server Components streaming behavior. The changes include new test infrastructure, CI integration, and test components.


✅ Positive Aspects

  1. Well-structured test infrastructure: The Playwright fixture pattern in fixture.ts is excellent - it provides clean abstractions for Redis control, page navigation, and snapshot matching.

  2. Comprehensive test coverage: The tests cover multiple scenarios (SSR, CSR, navigation, async components) and use snapshot testing effectively for complex UI states.

  3. Good CI integration: The new CircleCI job properly sets up Redis, Chrome, Rails server, and node renderer with appropriate health checks.

  4. Smart use of snapshots: ARIA snapshots are a great choice for verifying accessibility and DOM structure over time.


🔴 Critical Issues

1. Test files committed that should be development only ⚠️

Location: e2e-tests/dummy.spec.ts and e2e-tests/dummt-fixture.ts (note typo)

These appear to be experimental/scratch files with commented-out code and a typo in the filename. They should be removed:

```typescript
// dummy.spec.ts - contains only commented code and a test fixture experiment
// dummt-fixture.ts - filename typo suggests it's a temporary file
```

Recommendation: Remove these files before merging.


2. Missing await in streaming.spec.ts 🐛

Location: streaming.spec.ts:25, 29, 33, 37, 41, 55, 58, 61, 64, 67

```typescript
sendRedisItemValue(0, 'Incremental Value1'); // ❌ Missing await
await matchPageSnapshot('stage1');
```

This creates race conditions where snapshots may be taken before Redis values are actually sent. This could cause the exact flakiness the PR aims to fix!

Recommendation: Add `await` to all `sendRedisItemValue` calls.


3. React/JSX issues in RedisReceiver.jsx 🐛

Issue 3a - Invalid HTML attribute:
```jsx

// ❌ Should be className \`\`\`

Issue 3b - String interpolation in template literal:
```jsx

Waiting for the key "Item{itemIndex + 1}"

// ❌ Missing $ // Should be:

Waiting for the key "Item{itemIndex + 1}"

\`\`\`

Same issue appears on lines 8, 13, and 23.


4. Potential Ruby error handling issue ⚠️

Location: pages_controller.rb:97

```ruby
raise "request_id is required at the url" if @request_id.empty?
```

If `params[:request_id]` is `nil`, calling `.empty?` will raise `NoMethodError` instead of the intended error.

Recommendation:
```ruby
raise "request_id is required at the url" if @request_id.blank?
```


⚠️ Significant Issues

5. Missing file/dependency reference

Location: RedisReceiver.jsx:3

```jsx
import { listenToRequestData } from '../utils/redisReceiver';
```

This utility file is not included in the PR. Is it existing code, or should it be added?


6. TypeScript type accuracy

Location: fixture.ts:20

```typescript
sendRedisItemValue: (itemIndex: Number, value: unknown) => Promise;
```

Should use primitive `number` instead of object wrapper `Number`. This is a TypeScript anti-pattern.


7. Missing newline at end of files 📝

Per CLAUDE.md requirements, these files are missing trailing newlines:

  • All snapshot YAML files
  • `dummy.spec.ts`
  • `RedisReceiver.jsx`

8. Incomplete PR checklist

The PR description still contains template placeholders and unchecked items:

  • Add/update test to cover these changes
  • Update documentation
  • Update CHANGELOG file

💡 Minor Issues & Suggestions

9. Typo in test description

Location: streaming.spec.ts:10

```typescript
// "the best testing strategy" -> "is the best testing strategy"
```


10. Console.log statements

Location: fixture.ts:28, dummy.spec.ts:20

These should likely be removed or converted to proper debug logging before production.


11. Unnecessary directive (minor)

Location: RSCRoute.tsx:16

The `"use client"` directive was added, but this file already had the reference comment. Verify this is necessary - it might cause issues with the experimental React types.


12. CI job could be DRYer

The TODO comment at line 346 of .circleci/config.yml acknowledges duplication with the previous job. Consider extracting common steps.


13. Network request filtering edge case

Location: fixture.ts:79

```typescript
return (await page.requests()).filter(request => request.url().match(requestUrlPattern))
```

This filters all past requests. For tests checking "no requests made", this could include requests from previous test actions in the same page session.

Suggestion: Consider tracking requests from a specific point in time rather than all historical requests.


🔒 Security

No security concerns identified. The Redis test isolation using unique request IDs is good practice.


🎯 Performance

The Playwright tests should be faster and more reliable than the previous RSpec system tests. Good choice!


📋 Recommendations Summary

Before merging:

  1. ✅ Add `await` to all `sendRedisItemValue` calls
  2. ✅ Fix `class` → `className` in JSX
  3. ✅ Fix string interpolation syntax in JSX
  4. ✅ Remove `dummy.spec.ts` and `dummt-fixture.ts`
  5. ✅ Add trailing newlines to all files (run `rake autofix`)
  6. ✅ Run `bundle exec rubocop` and fix violations (per CLAUDE.md)
  7. ✅ Complete PR checklist items
  8. ✅ Fix Ruby `.empty?` → `.blank?` for nil safety
  9. ⚠️ Verify `listenToRequestData` import exists
  10. ⚠️ Consider fixing `Number` → `number` in TypeScript

Overall Assessment

This is a solid improvement that should eliminate test flakiness. The test infrastructure is well-designed. However, the missing `await` statements and JSX bugs are critical issues that must be fixed before merge, as they could reintroduce the exact flakiness this PR aims to solve.

Status: Needs changes

@claude
Copy link

claude bot commented Oct 20, 2025

Pull Request Review: Fix streaming flaky tests

Summary

This PR replaces the old Capybara/Selenium system tests with a modern Playwright-based E2E testing framework for testing streaming behavior. The implementation adds comprehensive tests for React Server Components (RSC) streaming with Redis, including a new demo component and well-structured test fixtures.

✅ Strengths

1. Excellent Test Architecture

  • Fixture pattern: The use of Playwright fixtures (fixture.ts) is well-designed with clear separation of concerns
  • Snapshot testing strategy: Using ARIA snapshots to validate incremental rendering stages is clever and maintainable
  • Comprehensive coverage: Tests cover multiple scenarios (direct render, router navigation, async components)
  • Reusability: Fixture composition allows running the same tests across different page configurations

2. Clean Component Design

  • Error boundaries: Properly wrapped in ErrorBoundary for fault tolerance
  • Suspense boundaries: Correct use of Suspense for progressive loading
  • Separation of concerns: Clear separation between data fetching (listenToRequestData) and rendering

3. CI/CD Integration

  • Proper CircleCI job configuration with Redis service
  • Playwright installation and dependencies management
  • Test result collection and artifact storage

🐛 Potential Issues

1. Critical: Incomplete cleanup in fixture.ts

Location: react_on_rails_pro/spec/dummy/e2e-tests/fixture.ts:36-39

redisRequestId: async ({ redisClient }, use) => {
  await use(randomUUID());
  redisClient.del  // ⚠️ INCOMPLETE LINE - no cleanup happening
},

Issue: This line appears to be incomplete - it references redisClient.del but doesn't actually call it. This will leave Redis streams uncleaned between tests.

Fix needed:

redisRequestId: async ({ redisClient }, use) => {
  const requestId = randomUUID();
  await use(requestId);
  // Clean up the stream after the test
  await redisClient.del(`stream:${requestId}`);
},

2. Resource Management: Redis Connection Leak Risk

Location: react_on_rails_pro/spec/dummy/client/app/utils/redisReceiver.ts

Issues:

  • Shared client without proper cleanup: Lines 36-37 create a shared Redis client that persists across requests
  • activeListeners cleanup: Line 318 sets activeListeners[requestId] = undefined instead of deleting the key
  • pendingPromises cleanup: Similar issue at lines 113, 325

Concerns:

  • Memory leaks from accumulating undefined values in objects
  • Shared Redis client may not be appropriate for SSR (multiple concurrent requests)
  • No error handling if Redis connection fails during SSR

Suggestions:

// Use delete instead of setting to undefined
delete activeListeners[requestId];
delete pendingPromises[key];

3. Thread Safety in Controller

Location: react_on_rails_pro/spec/dummy/app/controllers/pages_controller.rb:75-93

Issue: Using Thread.new in a Rails controller is generally discouraged because:

  • Thread safety issues with shared resources
  • Threads may outlive the request and cause issues in production
  • Better alternatives exist (background jobs, async frameworks)

Note: This appears to be demo/test code, so it's acceptable for that purpose, but should not be used as a pattern for production code.

4. Missing Newline at EOF

Multiple snapshot files are missing trailing newlines, which violates the CLAUDE.md requirement:

Files affected:

  • All __snapshots__/streaming.spec.ts/*.aria.yml files

Action required: Add newlines to pass linting

5. Hardcoded Timeout Values

Locations:

  • redisReceiver.ts:295 - 8 second timeout
  • pages_controller.rb:89 - 10 second timeout
  • streaming.spec.ts:118 - 3 second timeout

Suggestion: Consider extracting these to constants or configuration for easier tuning.

🔒 Security Considerations

1. No Major Security Issues

The code is defensive-oriented test infrastructure, which is acceptable.

2. Minor: Request ID Validation

Location: pages_controller.rb:96-97

@request_id = params[:request_id]
raise "request_id is required at the url" if @request_id.blank?

Suggestion: Consider validating the UUID format to prevent potential injection or abuse:

raise "Invalid request_id format" unless @request_id.match?(/\A[0-9a-f-]{36}\z/i)

🎯 Performance Considerations

1. Redis Stream Polling

The redisReceiver.ts implementation uses blocking reads with 1-second blocks (line 218). This is reasonable for the use case.

2. Message Deletion

Messages are deleted after processing (lines 149-161), which is good for preventing unbounded growth.

3. Test Parallelization

CI config sets workers: 1 for CI (line 27 in playwright.config.ts), which is conservative but safe. Consider increasing if tests remain reliable.

📋 Test Coverage

Excellent Coverage ✅

  • Incremental rendering at different stages
  • Early hydration behavior
  • Direct SSR vs. client-side routing
  • Async component rendering
  • Network request validation
  • ARIA accessibility snapshots

Potential Additions

  • Error scenarios (Redis connection failures, timeout handling)
  • Race condition testing (rapid navigation)
  • Memory leak detection for long-running sessions

📝 Documentation & Completeness

Missing (per PR checklist):

  • Update CHANGELOG file - Not updated
  • Update documentation - No documentation added
  • PR body not filled - Template sections still present

The PR body still contains the template placeholder text. Please update it with:

  • Description of the flaky test issues being fixed
  • Explanation of the migration from Capybara to Playwright
  • Any breaking changes or migration notes

🔧 Minor Issues

  1. RSCRoute.tsx - Added "use client" directive at line 14. Ensure this is intentional for RSCRoute component.

  2. Unused import? - fixture.ts:2 imports Response but it's only used as a return type. This is fine but worth noting.

✅ Recommendations

Must Fix:

  1. ❗ Complete the cleanup in fixture.ts:38 (redisClient.del)
  2. ❗ Add trailing newlines to all snapshot files
  3. ❗ Fill out the PR description properly

Should Fix:

  1. Replace undefined assignments with delete in resource cleanup
  2. Add request_id format validation
  3. Extract timeout constants

Nice to Have:

  1. Add error scenario tests
  2. Consider documenting the testing strategy in a README
  3. Add CHANGELOG entry

🎉 Conclusion

This is a solid improvement to the testing infrastructure. The Playwright migration is well-executed with thoughtful test design. The main concerns are:

  • The incomplete cleanup code that could cause test flakiness
  • Resource management patterns that could lead to memory leaks
  • Missing documentation updates

Once the critical issues are addressed (particularly the incomplete redisClient.del line), this will be a great addition to the codebase.

Overall Assessment: Approve pending fixes for critical issues ⚠️


Review generated with attention to the project's CLAUDE.md guidelines, focusing on RuboCop compliance, formatting requirements, and defensive security practices.

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

♻️ Duplicate comments (3)
react_on_rails_pro/spec/dummy/e2e-tests/fixture.ts (3)

33-34: Use quit()/disconnect() with node-redis 5.x, not close()

-    await use(client as RedisClientType);
-    await client.close();
+    await use(client as RedisClientType);
+    try {
+      await client.quit();
+    } catch {
+      await client.disconnect();
+    }

67-75: Use supported Playwright API to wait for console messages

Remove page.consoleMessages(); rely on waitForEvent.

-  waitForConsoleMessage: async({ page }, use) =>{
+  waitForConsoleMessage: async({ page }, use) =>{
     await use(async(msg) => {
-      if ((await page.consoleMessages()).find(consoleMsg => consoleMsg.text().includes(msg))) {
-        return;
-      }
-
       await page.waitForEvent('console', {
         predicate: (consoleMsg) => consoleMsg.text().includes(msg),
       })
     })
   },

78-82: Capture network requests via event listener; page.requests() doesn’t exist

Buffer requests with page.on('request').

-  getNetworkRequests: async({ page }, use) => {
-    await use(async(requestUrlPattern) => {
-      return (await page.requests()).filter(request => request.url().match(requestUrlPattern))
-    })
-  }
+  getNetworkRequests: async({ page }, use) => {
+    const captured: Request[] = [];
+    const onReq = (r: Request) => captured.push(r);
+    page.on('request', onReq);
+    await use(async (requestUrlPattern) => captured.filter(r => requestUrlPattern.test(r.url())));
+    page.off('request', onReq);
+  }
🧹 Nitpick comments (3)
react_on_rails_pro/spec/dummy/client/app/ror-auto-load-components/RedisReceiver.jsx (2)

21-24: Don’t pass children via prop (Biome noChildrenProp)

Use JSX children instead.

-const AsyncToggleContainer = async ({ children, childrenTitle, getValue }) => {
+const AsyncToggleContainer = async ({ children, childrenTitle, getValue }) => {
   await getValue('ToggleContainer');
-  return <ToggleContainer children={children} childrenTitle={childrenTitle} />
+  return <ToggleContainer childrenTitle={childrenTitle}>{children}</ToggleContainer>
 }

33-35: Avoid passing unused props to ToggleContainer

Only AsyncToggleContainer needs getValue. Pass conditionally.

-  const UsedToggleContainer = asyncToggleContainer ? AsyncToggleContainer : ToggleContainer;
-  const toggleContainerGetValueParam = asyncToggleContainer ? getValue : undefined;
+  const UsedToggleContainer = asyncToggleContainer ? AsyncToggleContainer : ToggleContainer;
@@
-          <UsedToggleContainer childrenTitle="Redis Items" getValue={toggleContainerGetValueParam}>
+          <UsedToggleContainer
+            childrenTitle="Redis Items"
+            {...(asyncToggleContainer ? { getValue } : {})}
+          >

Also applies to: 41-41

react_on_rails_pro/spec/dummy/e2e-tests/fixture.ts (1)

27-31: Replace empty object pattern param (Biome noEmptyPattern)

Avoid {} destructuring; use _ placeholder.

-  redisClient: [async ({}, use, workerInfo) => {
+  redisClient: [async (_opts, use, workerInfo) => {
     console.log(`Creating Redis Client at Worker ${workerInfo.workerIndex}`)
     const url = process.env.REDIS_URL || 'redis://localhost:6379';
     const client = createClient({ url });
     await client.connect();
📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 6064b02 and 5d7cfa2.

📒 Files selected for processing (6)
  • .circleci/config.yml (2 hunks)
  • react_on_rails_pro/spec/dummy/app/controllers/pages_controller.rb (1 hunks)
  • react_on_rails_pro/spec/dummy/client/app/ror-auto-load-components/RedisReceiver.jsx (1 hunks)
  • react_on_rails_pro/spec/dummy/e2e-tests/fixture.ts (1 hunks)
  • react_on_rails_pro/spec/dummy/e2e-tests/streaming.spec.ts (1 hunks)
  • react_on_rails_pro/spec/dummy/playwright.config.ts (1 hunks)
🚧 Files skipped from review as they are similar to previous changes (4)
  • react_on_rails_pro/spec/dummy/app/controllers/pages_controller.rb
  • react_on_rails_pro/spec/dummy/playwright.config.ts
  • .circleci/config.yml
  • react_on_rails_pro/spec/dummy/e2e-tests/streaming.spec.ts
🧰 Additional context used
📓 Path-based instructions (2)
**/*.{js,jsx,ts,tsx,css,scss,json,yml,yaml,md}

📄 CodeRabbit inference engine (CLAUDE.md)

Prettier is the sole authority for formatting all non-Ruby files; never manually format them

Files:

  • react_on_rails_pro/spec/dummy/e2e-tests/fixture.ts
  • react_on_rails_pro/spec/dummy/client/app/ror-auto-load-components/RedisReceiver.jsx
**/*.{js,jsx,ts,tsx}

📄 CodeRabbit inference engine (CLAUDE.md)

Use ESLint for JS/TS code (lint via rake lint or yarn lint)

Files:

  • react_on_rails_pro/spec/dummy/e2e-tests/fixture.ts
  • react_on_rails_pro/spec/dummy/client/app/ror-auto-load-components/RedisReceiver.jsx
🧬 Code graph analysis (2)
react_on_rails_pro/spec/dummy/e2e-tests/fixture.ts (1)
react_on_rails_pro/spec/dummy/client/app/ror-auto-load-components/RedisReceiver.jsx (1)
  • value (7-7)
react_on_rails_pro/spec/dummy/client/app/ror-auto-load-components/RedisReceiver.jsx (2)
react_on_rails_pro/spec/dummy/client/app/components/RSCPostsPage/ToggleContainer.jsx (1)
  • ToggleContainer (3-19)
react_on_rails_pro/spec/dummy/client/app/components/ErrorBoundary.tsx (1)
  • ErrorBoundary (7-9)
🪛 Biome (2.1.2)
react_on_rails_pro/spec/dummy/e2e-tests/fixture.ts

[error] 27-27: Unexpected empty object pattern.

(lint/correctness/noEmptyPattern)

react_on_rails_pro/spec/dummy/client/app/ror-auto-load-components/RedisReceiver.jsx

[error] 23-23: Avoid passing children using a prop

The canonical way to pass children in React is to use JSX elements

(lint/correctness/noChildrenProp)

⏰ 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: dummy-app-integration-tests (3.2, 20)
  • GitHub Check: dummy-app-integration-tests (3.4, 22)
  • GitHub Check: rspec-package-tests (3.4, minimum)
  • GitHub Check: build
  • GitHub Check: rspec-package-tests (3.2, minimum)
  • GitHub Check: claude-review
  • GitHub Check: rspec-package-tests (3.4, latest)
  • GitHub Check: rspec-package-tests (3.2, latest)
  • GitHub Check: build-and-test

Comment on lines 6 to 9
const RedisItem = async ({ getValue, itemIndex }) => {
const value = await getValue(`Item${itemIndex}`);
return <li className={`redis-item${itemIndex}`}>Value of "Item{itemIndex + 1}": {value}</li>
}
Copy link
Contributor

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🔴 Critical

Fix JSX interpolation: avoid braces inside quotes

Renders literal Item{itemIndex + 1}. Interpolate via template or split nodes.

-const RedisItem = async ({ getValue, itemIndex }) => {
-  const value = await getValue(`Item${itemIndex}`);
-  return <li className={`redis-item${itemIndex}`}>Value of "Item{itemIndex + 1}": {value}</li>
-}
+const RedisItem = async ({ getValue, itemIndex }) => {
+  const value = await getValue(`Item${itemIndex}`);
+  return (
+    <li className={`redis-item${itemIndex}`}>
+      {`Value of "Item${itemIndex + 1}": ${String(value)}`}
+    </li>
+  );
+}
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
const RedisItem = async ({ getValue, itemIndex }) => {
const value = await getValue(`Item${itemIndex}`);
return <li className={`redis-item${itemIndex}`}>Value of "Item{itemIndex + 1}": {value}</li>
}
const RedisItem = async ({ getValue, itemIndex }) => {
const value = await getValue(`Item${itemIndex}`);
return (
<li className={`redis-item${itemIndex}`}>
{`Value of "Item${itemIndex + 1}": ${String(value)}`}
</li>
);
}
🤖 Prompt for AI Agents
In
react_on_rails_pro/spec/dummy/client/app/ror-auto-load-components/RedisReceiver.jsx
around lines 6 to 9, the JSX is rendering the literal text Item{itemIndex + 1}
because the braces are inside a quoted string; change the node so the
interpolation is evaluated — e.g. replace the quoted "Item{itemIndex + 1}" with
a JSX expression such as {`Item${itemIndex + 1}`} or {"Item" + (itemIndex + 1)}
so the correct item number is displayed (keep the rest of the element as-is).

Comment on lines 11 to 16
const RedisItemWithWrapper = ({ getValue, itemIndex }) => (
<section className={`redis-item${itemIndex}-container`}>
<Suspense fallback={<p className={`redis-item${itemIndex}-fallback`}>Waiting for the key "Item{itemIndex + 1}"</p>}>
<RedisItem getValue={getValue} itemIndex={itemIndex} />
</Suspense>
</section>
Copy link
Contributor

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟠 Major

Fix Suspense fallback text interpolation

Same issue; braces are inside quotes.

-  <section className={`redis-item${itemIndex}-container`}>
-    <Suspense fallback={<p className={`redis-item${itemIndex}-fallback`}>Waiting for the key "Item{itemIndex + 1}"</p>}>
+  <section className={`redis-item${itemIndex}-container`}>
+    <Suspense
+      fallback={
+        <p className={`redis-item${itemIndex}-fallback`}>
+          {`Waiting for the key "Item${itemIndex + 1}"`}
+        </p>
+      }
+    >
       <RedisItem getValue={getValue} itemIndex={itemIndex} />
     </Suspense>
   </section>
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
const RedisItemWithWrapper = ({ getValue, itemIndex }) => (
<section className={`redis-item${itemIndex}-container`}>
<Suspense fallback={<p className={`redis-item${itemIndex}-fallback`}>Waiting for the key "Item{itemIndex + 1}"</p>}>
<RedisItem getValue={getValue} itemIndex={itemIndex} />
</Suspense>
</section>
const RedisItemWithWrapper = ({ getValue, itemIndex }) => (
<section className={`redis-item${itemIndex}-container`}>
<Suspense
fallback={
<p className={`redis-item${itemIndex}-fallback`}>
{`Waiting for the key "Item${itemIndex + 1}"`}
</p>
}
>
<RedisItem getValue={getValue} itemIndex={itemIndex} />
</Suspense>
</section>
)
🤖 Prompt for AI Agents
In
react_on_rails_pro/spec/dummy/client/app/ror-auto-load-components/RedisReceiver.jsx
around lines 11 to 16, the Suspense fallback text uses literal braces inside a
quoted string ("Item{itemIndex + 1}") so it renders the braces instead of
interpolating the expression; change the fallback to use a JSX expression that
interpolates itemIndex (for example wrap a template literal or concatenation in
curly braces) so it renders the evaluated Item number while keeping the existing
className template literal unchanged.

@claude
Copy link

claude bot commented Oct 20, 2025

PR Review: Fix streaming flaky tests

Summary

This PR adds comprehensive Playwright-based E2E tests for streaming functionality with Redis, introducing a robust testing infrastructure for React Server Components streaming behavior. The implementation includes CI/CD integration and well-structured test fixtures.


✅ Code Quality & Best Practices

Strengths

  • Excellent test organization: The fixture pattern using Playwright's test.extend() is well-designed and promotes reusability
  • Strong TypeScript typing: All fixtures and utilities are properly typed
  • Good separation of concerns: Test fixtures, utilities, and tests are cleanly separated
  • Comprehensive test coverage: Tests cover multiple scenarios including SSR, navigation, async components, and hydration
  • Snapshot testing strategy: Using ARIA snapshots is a smart choice for verifying incremental rendering behavior

Areas for Improvement

Critical Issues:

  1. Undefined variable in RedisReceiver.jsx:42

    {...(toggleContainerGetValueParam ? { getValue } : {})}

    The variable toggleContainerGetValueParam is not defined anywhere. This will cause a runtime error.

    • Location: react_on_rails_pro/spec/dummy/client/app/ror-auto-load-components/RedisReceiver.jsx:42
    • Fix: Either define this variable or remove this conditional spread if it's not needed
  2. Incomplete cleanup in fixture.ts:38

    redisRequestId: async ({ redisClient }, use) => {
      await use(randomUUID());
      redisClient.del  // ← Incomplete statement
    },

    This line does nothing and appears to be unfinished code.

    • Location: react_on_rails_pro/spec/dummy/e2e-tests/fixture.ts:36-39
    • Fix: Either complete the cleanup logic to delete Redis streams or remove this line

Code Quality Issues:

  1. Missing PR checklist completion
    The PR description still contains the template with unchecked items:

    • Add/update test to cover these changes ✓ (done, but not checked)
    • Update documentation (should this be checked?)
    • Update CHANGELOG file (required per CLAUDE.md)
  2. Magic numbers without explanation

    • setTimeout(..., 8000) in redisReceiver.ts:295 - why 8 seconds?
    • Consider making this configurable or at least add a comment explaining the timeout duration
  3. Console.log in production code

    • fixture.ts:28 has a console.log that should use proper logging or be removed for production

🐛 Potential Bugs

  1. Race condition in Redis client connection (redisReceiver.ts:48-60)

    • Multiple concurrent calls to getRedisClient() could create multiple connections before isClientConnected is set
    • Recommendation: Use a connection promise or mutex to ensure only one connection is established
  2. Memory leak potential (redisReceiver.ts:40-43)

    • activeListeners and pendingPromises are module-level objects that never clean up undefined entries
    • Recommendation: Use a Map and delete keys instead of setting to undefined, or periodically clean up
  3. Recursive call without exit guarantee (redisReceiver.ts:236)

    void readStream();  // Recursive call in finally block

    While isActive and isEnded should prevent this, consider adding a max retry count as a safety measure

  4. Missing await in async IIFE (redisReceiver.ts:247-256)

    • The immediately-invoked async function has .catch() handling, which is good, but errors are only logged
    • Consider exposing initialization status to the consumer

🔒 Security Concerns

  1. Redis URL from environment

    • Both redisReceiver.ts and fixture.ts read REDIS_URL from environment
    • ✓ This is acceptable for testing, but ensure .env files are in .gitignore
  2. No authentication/authorization in test endpoints

    • redis_receiver_for_testing controller action accepts any request_id parameter
    • ✓ Acceptable for test/dummy app, but ensure this route is not exposed in production
  3. SecureRandom.uuid usage (pages_controller.rb:72)

    • ✓ Good use of SecureRandom for request ID generation

⚡ Performance Considerations

  1. Redis polling with BLOCK (redisReceiver.ts:218)

    • Using BLOCK: 1000 (1 second) is reasonable, but could be optimized
    • Consider making block duration configurable based on use case
  2. Message deletion strategy

    • Messages are deleted individually after processing, which is good for preventing replay
    • ✓ Efficient approach using xDel with batched IDs
  3. Shared Redis client (redisReceiver.ts:36)

    • ✓ Good optimization to reuse connections
    • Reference counting via activeListeners is a smart approach
  4. CI Performance

    • Setting workers: 1 in CI (playwright.config.ts:27) is conservative
    • Consider testing with workers: 2 to balance speed vs. flakiness

🧪 Test Coverage

Excellent Coverage:

  • ✅ Incremental rendering scenarios
  • ✅ Early hydration behavior
  • ✅ SSR vs. client-side rendering
  • ✅ Navigation scenarios
  • ✅ Async component rendering
  • ✅ Out-of-order data arrival (Item 0, 3, 1, 2, 4)

Consider Adding:

  • ❓ Error scenarios (Redis connection failures, timeouts)
  • ❓ Cleanup verification (are Redis streams properly deleted after tests?)
  • ❓ Concurrent request handling (multiple request IDs simultaneously)

📋 Action Items

Must Fix Before Merge:

  1. Fix undefined toggleContainerGetValueParam variable (RedisReceiver.jsx:42)
  2. Complete or remove incomplete redisClient.del statement (fixture.ts:38)
  3. Update CHANGELOG file (per repository requirements in CLAUDE.md)
  4. Complete PR checklist in description

Should Fix:

  1. Add race condition protection in getRedisClient()
  2. Clean up memory leak potential in module-level objects
  3. Remove or properly handle console.log in fixture.ts:28
  4. Add comments explaining magic numbers (8 second timeout)

Consider:

  1. Add error scenario test cases
  2. Document the Redis stream cleanup strategy
  3. Add max retry safeguard in recursive readStream()

💭 Additional Notes

  • The "use client" directive added to RSCRoute.tsx:14 is appropriate for this experimental React feature
  • CircleCI configuration looks solid with proper caching and parallel job execution
  • The playwright configuration supports multiple browsers, which is excellent for catching browser-specific issues
  • The addition of ARIA snapshot files provides good visual regression testing

Overall Assessment: This is a well-architected addition to the test suite that significantly improves confidence in streaming functionality. The critical bugs (undefined variable and incomplete cleanup) must be fixed before merge, but the overall approach is sound.

🤖 Generated with Claude Code

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: 2

♻️ Duplicate comments (3)
react_on_rails_pro/spec/dummy/client/app/ror-auto-load-components/RedisReceiver.jsx (3)

6-9: Fix JSX interpolation: text renders literal braces.

Line 8 displays "Item{itemIndex + 1}" as literal text because the braces are inside a string. Users will see Item{itemIndex + 1} instead of Item1, Item2, etc.

Apply this diff to interpolate the expression:

 const RedisItem = async ({ getValue, itemIndex }) => {
   const value = await getValue(`Item${itemIndex}`);
-  return <li className={`redis-item${itemIndex}`}>Value of "Item{itemIndex + 1}": {value}</li>
+  return <li className={`redis-item${itemIndex}`}>{`Value of "Item${itemIndex + 1}": ${value}`}</li>
 }

11-17: Fix Suspense fallback interpolation.

Line 13 has the same issue: "Item{itemIndex + 1}" renders as literal text in the fallback message.

Apply this diff:

 const RedisItemWithWrapper = ({ getValue, itemIndex }) => (
   <section className={`redis-item${itemIndex}-container`}>
-    <Suspense fallback={<p className={`redis-item${itemIndex}-fallback`}>Waiting for the key "Item{itemIndex + 1}"</p>}>
+    <Suspense fallback={<p className={`redis-item${itemIndex}-fallback`}>{`Waiting for the key "Item${itemIndex + 1}"`}</p>}>
       <RedisItem getValue={getValue} itemIndex={itemIndex} />
     </Suspense>
   </section>
 )

29-31: Guard railsContext before using in operator.

If railsContext is undefined or null, line 29 throws a TypeError. Past review marked this as addressed, but the guard is still missing.

Apply this diff:

-  if ('addPostSSRHook' in railsContext) {
+  if (railsContext && 'addPostSSRHook' in railsContext) {
     railsContext.addPostSSRHook(close);
   }
📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 5d7cfa2 and 3954ee2.

📒 Files selected for processing (2)
  • .circleci/config.yml (2 hunks)
  • react_on_rails_pro/spec/dummy/client/app/ror-auto-load-components/RedisReceiver.jsx (1 hunks)
🚧 Files skipped from review as they are similar to previous changes (1)
  • .circleci/config.yml
🧰 Additional context used
📓 Path-based instructions (2)
**/*.{js,jsx,ts,tsx,css,scss,json,yml,yaml,md}

📄 CodeRabbit inference engine (CLAUDE.md)

Prettier is the sole authority for formatting all non-Ruby files; never manually format them

Files:

  • react_on_rails_pro/spec/dummy/client/app/ror-auto-load-components/RedisReceiver.jsx
**/*.{js,jsx,ts,tsx}

📄 CodeRabbit inference engine (CLAUDE.md)

Use ESLint for JS/TS code (lint via rake lint or yarn lint)

Files:

  • react_on_rails_pro/spec/dummy/client/app/ror-auto-load-components/RedisReceiver.jsx
🧬 Code graph analysis (1)
react_on_rails_pro/spec/dummy/client/app/ror-auto-load-components/RedisReceiver.jsx (2)
react_on_rails_pro/spec/dummy/client/app/components/RSCPostsPage/ToggleContainer.jsx (1)
  • ToggleContainer (3-19)
react_on_rails_pro/spec/dummy/client/app/components/ErrorBoundary.tsx (1)
  • ErrorBoundary (7-9)
⏰ 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). (8)
  • GitHub Check: dummy-app-integration-tests (3.4, 22)
  • GitHub Check: dummy-app-integration-tests (3.2, 20)
  • GitHub Check: build
  • GitHub Check: rspec-package-tests (3.4, latest)
  • GitHub Check: rspec-package-tests (3.2, minimum)
  • GitHub Check: rspec-package-tests (3.2, latest)
  • GitHub Check: rspec-package-tests (3.4, minimum)
  • GitHub Check: build-and-test
🔇 Additional comments (2)
react_on_rails_pro/spec/dummy/client/app/ror-auto-load-components/RedisReceiver.jsx (2)

1-4: LGTM: Imports are clean.

All imports are properly structured and relevant to the component's functionality.


19-24: LGTM: AsyncToggleContainer implementation is correct.

The async wrapper pattern properly awaits the getValue call before rendering the ToggleContainer.

@claude
Copy link

claude bot commented Oct 20, 2025

Pull Request Review - Fix Streaming Flaky Tests

Thank you for working on fixing the flaky streaming tests! This is an important improvement to the test suite. I've reviewed the changes and have the following feedback:


🎯 Overall Assessment

Positives:

  • Excellent approach using Playwright E2E tests with Redis fixtures for deterministic testing
  • Well-structured test fixtures with reusable components
  • Comprehensive test coverage across multiple scenarios (SSR, CSR, navigation, async components)
  • Good use of snapshot testing for streaming behavior validation

Critical Issues to Address:

  • Undefined variable causing runtime errors
  • Missing checklist items in PR description
  • Potential resource cleanup issues

🐛 Bugs & Issues

Critical: Undefined Variable in RedisReceiver.jsx (Line 42)

The variable toggleContainerGetValueParam is referenced but never defined, which will cause a ReferenceError at runtime.

Location: react_on_rails_pro/spec/dummy/client/app/ror-auto-load-components/RedisReceiver.jsx:42

Fix: This should likely use the asyncToggleContainer prop instead, or be removed entirely.


Question: "use client" directive in RSCRoute.tsx

Why is the "use client" directive being added to RSCRoute.tsx? This seems counterintuitive for a component dealing with RSC (React Server Components). Please clarify if this is intentional and document the reasoning.

Location: packages/react-on-rails-pro/src/RSCRoute.tsx:16


⚠️ Potential Issues

1. Race Condition in Redis Thread

In pages_controller.rb:redis_receiver, the thread timeout handling could be improved with more descriptive error messages and logging of thread status on timeout.

2. Redis Client Connection Management

In redisReceiver.ts, the shared Redis client pattern could lead to connection leaks if a listener's close() method is never called (e.g., component unmounts abruptly, error in SSR).

Suggestions:

  • Add connection timeout/TTL
  • Consider using a connection pool
  • Add cleanup hook in test teardown
  • Log when connections are not being closed

3. Error Swallowing in Async IIFE

Lines 247-256 in redisReceiver.ts log errors but don't propagate them. This could mask initialization failures. Consider exposing initialization status.


🔒 Security Concerns

1. Redis Stream Key Injection

While requestId is generated with randomUUID(), ensure that in production code, any user-controlled input used in Redis keys is properly validated/sanitized.

2. Controller Parameter Validation

Good that validation is present in redis_receiver_for_testing. Consider adding format validation (UUID pattern) to prevent injection.


📊 Performance Considerations

  • Snapshot files are appropriately sized
  • Redis polling with COUNT: 100 seems high for reading 5 items - consider reducing to COUNT: 10 for efficiency
  • CI job looks well-configured, but consider adding a timeout to prevent hanging jobs
  • The TODO comment about DRY should be addressed in a follow-up

Test Coverage

Excellent coverage of:

  • Incremental rendering across 4 scenarios
  • Early hydration behavior
  • SSR vs CSR payload fetching
  • Navigation scenarios
  • Async client component rendering

Suggestions:

  1. Add test for error cases (Redis connection failure, timeout)
  2. Add test for rapid navigation (component unmount during streaming)
  3. Consider testing cleanup behavior (connection closure)

📝 PR Checklist Items

The PR description indicates incomplete checklist items:

  • Add/update test to cover these changes ✅ DONE (Playwright tests added)
  • Update documentation ❌ MISSING
  • Update CHANGELOG file ❌ MISSING

Action Required:

  1. Update CHANGELOG with entry at the top
  2. Add documentation for the new E2E test infrastructure (if applicable)
  3. Update the PR description with a proper summary (currently has template text)

🎨 Code Quality

Positive:

  • Good use of TypeScript for type safety in fixtures
  • Clean separation of concerns
  • Descriptive test names
  • Good error handling in Ruby controller

Suggestions:

  1. RedisReceiver.jsx: Add PropTypes or convert to TypeScript
  2. Consider consistency in naming conventions
  3. Magic numbers like [0,1,2,3,4] could use Array.from for clarity
  4. Consider moving fixture types to a separate types.ts file

🚀 Recommendations

Before Merge:

  1. ✅ Fix the toggleContainerGetValueParam undefined variable bug (CRITICAL)
  2. ✅ Clarify/document the "use client" directive addition
  3. ✅ Fill out PR description summary
  4. ✅ Add CHANGELOG entry
  5. ✅ Run bundle exec rubocop and fix any violations (per CLAUDE.md)
  6. ✅ Ensure all files end with newlines (per CLAUDE.md)

Nice to Have (Follow-up PRs):

  • Address the DRY TODO in CircleCI config
  • Add error case testing
  • Improve Redis connection lifecycle management
  • Convert RedisReceiver.jsx to TypeScript

💯 Summary

This PR represents a significant improvement to test reliability by replacing flaky integration tests with deterministic E2E tests using Playwright and Redis fixtures. The approach is sound and the test coverage is comprehensive.

Main blocking issue: Fix the undefined toggleContainerGetValueParam variable before merging.

Once the critical bug is fixed and the checklist items are completed, this will be ready to merge! 🚀


Great work on tackling flaky tests! Let me know if you need clarification on any of these points.

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: 1

♻️ Duplicate comments (3)
react_on_rails_pro/spec/dummy/e2e-tests/fixture.ts (3)

36-38: Add cleanup for Redis stream keys.

The fixture generates a request ID but doesn't clean up the associated Redis stream key (stream:${id}). Over time, this will accumulate test data in Redis.

Apply this diff:

 redisRequestId: async ({ redisClient }, use) => {
-  await use(randomUUID());
+  const id = randomUUID();
+  try {
+    await use(id);
+  } finally {
+    // Clean up the stream for this test
+    await redisClient.del(`stream:${id}`);
+  }
 },

77-81: Replace invalid Playwright API with event listener.

page.requests() is not a Playwright API and will cause runtime errors. Network requests must be captured via an event listener.

Apply this diff:

 getNetworkRequests: async({ page }, use) => {
+  const captured: Request[] = [];
+  const onRequest = (r: Request) => captured.push(r);
+  page.on('request', onRequest);
   await use(async(requestUrlPattern) => {
-    return (await page.requests()).filter(request => request.url().match(requestUrlPattern))
+    return captured.filter(request => request.url().match(requestUrlPattern));
   })
+  page.off('request', onRequest);
 }

66-75: Remove invalid Playwright API call.

page.consoleMessages() is not a Playwright API and will cause runtime errors. The pre-check is unnecessary since waitForEvent will resolve immediately if the event has already fired.

Apply this diff:

 waitForConsoleMessage: async({ page }, use) =>{
   await use(async(msg) => {
-    if ((await page.consoleMessages()).find(consoleMsg => consoleMsg.text().includes(msg))) {
-      return;
-    }
-
     await page.waitForEvent('console', {
       predicate: (consoleMsg) => consoleMsg.text().includes(msg),
     })
   })
 },
🧹 Nitpick comments (1)
react_on_rails_pro/spec/dummy/app/controllers/pages_controller.rb (1)

72-99: Consider capturing @request_id locally in the thread.

While the current implementation is safe (the thread is joined before the action completes), it's clearer to capture instance variables as local variables when passing them to threads to avoid potential confusion.

Apply this diff:

 def redis_receiver
   @request_id = SecureRandom.uuid
-
+  request_id = @request_id
+
   redis_thread = Thread.new do
     redis = ::Redis.new
     5.times do |index|
       sleep 1
-      redis.xadd("stream:#{@request_id}", { ":Item#{index}" => "Value of Item#{index + 1}".to_json })
+      redis.xadd("stream:#{request_id}", { ":Item#{index}" => "Value of Item#{index + 1}".to_json })
     end
   rescue StandardError => e
📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 3954ee2 and 8d4b4c4.

📒 Files selected for processing (2)
  • react_on_rails_pro/spec/dummy/app/controllers/pages_controller.rb (1 hunks)
  • react_on_rails_pro/spec/dummy/e2e-tests/fixture.ts (1 hunks)
🧰 Additional context used
📓 Path-based instructions (3)
{Gemfile,Rakefile,config.ru,**/*.{rb,rake,gemspec,ru}}

📄 CodeRabbit inference engine (CLAUDE.md)

{Gemfile,Rakefile,config.ru,**/*.{rb,rake,gemspec,ru}}: All Ruby code must pass RuboCop with zero offenses before commit/push
RuboCop is the sole authority for Ruby file formatting; never manually format Ruby files

Files:

  • react_on_rails_pro/spec/dummy/app/controllers/pages_controller.rb
**/*.{js,jsx,ts,tsx,css,scss,json,yml,yaml,md}

📄 CodeRabbit inference engine (CLAUDE.md)

Prettier is the sole authority for formatting all non-Ruby files; never manually format them

Files:

  • react_on_rails_pro/spec/dummy/e2e-tests/fixture.ts
**/*.{js,jsx,ts,tsx}

📄 CodeRabbit inference engine (CLAUDE.md)

Use ESLint for JS/TS code (lint via rake lint or yarn lint)

Files:

  • react_on_rails_pro/spec/dummy/e2e-tests/fixture.ts
🧬 Code graph analysis (2)
react_on_rails_pro/spec/dummy/app/controllers/pages_controller.rb (1)
react_on_rails_pro/lib/react_on_rails_pro/concerns/stream.rb (1)
  • stream_view_containing_react_components (33-47)
react_on_rails_pro/spec/dummy/e2e-tests/fixture.ts (1)
react_on_rails_pro/spec/dummy/client/app/ror-auto-load-components/RedisReceiver.jsx (1)
  • value (7-7)
🪛 Biome (2.1.2)
react_on_rails_pro/spec/dummy/e2e-tests/fixture.ts

[error] 27-27: Unexpected empty object pattern.

(lint/correctness/noEmptyPattern)

⏰ 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: dummy-app-integration-tests (3.4, 22)
  • GitHub Check: dummy-app-integration-tests (3.2, 20)
  • GitHub Check: build
  • GitHub Check: rspec-package-tests (3.2, minimum)
  • GitHub Check: rspec-package-tests (3.4, latest)
  • GitHub Check: rspec-package-tests (3.2, latest)
  • GitHub Check: rspec-package-tests (3.4, minimum)
  • GitHub Check: claude-review
  • GitHub Check: build-and-test

@claude
Copy link

claude bot commented Oct 20, 2025

PR Review: Fix streaming flaky tests

Overview

This PR adds Playwright-based end-to-end tests for streaming functionality using Redis, replacing flaky integration tests. The implementation includes a new RedisReceiver component, test fixtures, and CI pipeline integration.

Strengths

Excellent Test Architecture

  • Well-designed Playwright fixtures with proper separation of concerns
  • Smart use of ARIA snapshot testing to validate incremental rendering stages
  • Comprehensive test coverage across different scenarios (direct page, router navigation, async components)
  • Proper cleanup with finally blocks for Redis streams

Good Code Organization

  • Clear separation between test fixtures and test specs
  • Reusable test utilities (sendRedisValue, matchPageSnapshot, waitForConsoleMessage)
  • Proper TypeScript types for fixtures

CI/CD Integration

  • New CI job properly configured with Redis service
  • Playwright dependencies installation included
  • Test results and artifacts properly stored

Issues Found

🔴 Critical Issues

1. Missing File in Repository
The RedisReceiver component imports from ../utils/redisReceiver.ts (line 3 of RedisReceiver.jsx), but this file was not included in the diff. This would cause import failures.

  • Location: react_on_rails_pro/spec/dummy/client/app/ror-auto-load-components/RedisReceiver.jsx:3
  • Action Required: Ensure client/app/utils/redisReceiver.ts is included in the PR

2. Missing Newline at EOF
Multiple snapshot files are missing trailing newlines, which violates the CLAUDE.md requirement: "ALWAYS ensure files end with a newline character"

  • Affected files: All __snapshots__/streaming.spec.ts/*.aria.yml files
  • Action Required: Add newlines to all snapshot files before commit

3. Incomplete PR Description
The PR description still contains placeholder text ("Remove this paragraph...") and hasn't been filled out with:

  • Description of what was fixed
  • Reference to the bug/issue being addressed
  • Missing checkboxes for tests, docs, and CHANGELOG

🟡 Major Issues

4. Potential Redis Connection Leak
In redisReceiver.ts:36-70, the shared Redis client connection is never cleaned up if all listeners close but new ones are created later. The isClientConnected flag remains true even after quit().

async function closeRedisClient() {
  if (sharedRedisClient && isClientConnected) {
    await sharedRedisClient.quit();
    isClientConnected = false;
    // Missing: sharedRedisClient = null;
  }
}
  • Location: redisReceiver.ts:65-70
  • Fix: Add sharedRedisClient = null; after quit()

5. Missing Error Boundary Import
RedisReceiver.jsx:4 imports ErrorBoundary from ../components/ErrorBoundary, but it's unclear if this file exists or was added.

  • Action: Verify the ErrorBoundary component exists

6. JSX Type Safety Issue
In RedisReceiver.jsx:8, string interpolation in JSX uses incorrect syntax:

return <li>Value of "Item{itemIndex + 1}": {value}</li>

Should be:

return <li>Value of "Item{itemIndex + 1}": {value}</li>

The issue is "Item{itemIndex + 1}" - the curly braces inside the string won't interpolate.

7. React Server Component Pattern Issue
RedisReceiver returns a function component (line 35-53), which is an unusual pattern. This might cause issues with React Server Components. Should either:

  • Return JSX directly, or
  • Be explicitly documented why this pattern is necessary

8. Missing "use client" Directive
The RedisReceiver.jsx component uses React hooks (Suspense, ErrorBoundary) but doesn't have a "use client" directive at the top, which could cause SSR issues with React Server Components.

🟢 Minor Issues

9. Inconsistent Naming

  • Component file is .jsx but uses TypeScript patterns (async components)
  • Consider renaming to .tsx for consistency

10. Missing PropTypes/TypeScript
RedisReceiver component lacks type definitions for props. Consider:

interface RedisReceiverProps {
  requestId: string;
  asyncToggleContainer?: boolean;
}

11. Hard-coded Array Mapping
Line 46: [0,1,2,3,4].map(...) - should use Array.from({ length: 5 }) or constant for maintainability

12. Magic Numbers

  • Timeout of 8000ms in redisReceiver.ts:295 should be a named constant
  • Sleep of 1 second in controller (line 78) should be configurable or documented

13. Console.log in Production Code
fixture.ts:28 has a console.log that should use a proper logger or be removed

14. TODO Comment in CI Config
.circleci/config.yml:346 has # TODO: DRY with previous job - should be addressed or tracked in an issue


Performance Considerations

⚠️ Redis Polling Performance
The readStream() function uses BLOCK: 1000 (1 second) which is reasonable, but creates a continuous polling loop. Consider:

  • Document the performance impact
  • Add metrics/logging for monitoring

⚠️ Shared Redis Client
Good use of connection pooling with shared client, but could be improved:

  • Add connection retry logic
  • Add connection health checks

Security Concerns

🔒 Redis Connection String
The default Redis URL redis://localhost:6379 should:

  • Not be hard-coded in production code
  • Be documented in setup instructions
  • Consider authentication requirements

🔒 Request ID Validation
redis_receiver_for_testing controller accepts request_id from params without validation (line 102). Could potentially be exploited for:

  • Redis key enumeration
  • Stream hijacking
    Consider:
  • UUID format validation
  • Rate limiting
  • Request ID signing/verification

Test Coverage Assessment

Well Covered:

  • Incremental rendering scenarios
  • Navigation scenarios
  • Early hydration behavior
  • Network request verification

Missing Coverage:

  • Error handling scenarios (Redis connection failure, timeout)
  • Concurrent request handling
  • Stream cleanup on navigation away
  • Large payload handling

Recommendations

Before Merging:

  1. ✅ Run bundle exec rubocop and fix all violations (per CLAUDE.md)
  2. ✅ Add trailing newlines to all snapshot files
  3. ✅ Fill out PR description completely
  4. ✅ Include the missing redisReceiver.ts utility file
  5. ✅ Fix the Redis client cleanup bug
  6. ✅ Verify ErrorBoundary exists
  7. ✅ Fix JSX string interpolation issue

Post-Merge Improvements:

  1. Add error scenario tests
  2. Address the TODO in CI config (DRY improvement)
  3. Add TypeScript types to RedisReceiver
  4. Consider extracting magic numbers to constants
  5. Add CHANGELOG entry

Verdict

Changes Requested ⚠️

The test infrastructure is excellent and the approach is sound, but there are several critical issues that need to be addressed:

  • Missing required file in the PR
  • Formatting violations (missing newlines)
  • Potential Redis connection leak
  • Incomplete PR description

Once these issues are resolved, this will be a solid improvement to test reliability.


Great work on the test architecture! The use of Playwright fixtures and ARIA snapshots is particularly well done. The incremental testing approach is exactly what's needed for validating streaming behavior.

@claude
Copy link

claude bot commented Oct 20, 2025

PR Review: Fix streaming flaky tests

Summary

This PR replaces legacy RSpec integration tests with modern Playwright end-to-end tests for streaming server rendering. The implementation adds Redis-backed streaming demos and comprehensive test coverage using ARIA snapshot testing.

Strengths

Test Architecture (Excellent)

  • Well-designed fixture system with composable test contexts
  • Smart use of ARIA snapshots for both rendering order AND accessibility validation
  • Proper resource cleanup in fixture finally blocks
  • Worker-scoped Redis client for efficient resource sharing

Test Coverage (Comprehensive)

Covers critical scenarios:

  • Incremental rendering with out-of-order data arrival
  • Early hydration before stream completion
  • Server-side vs client-side rendering behavior
  • Router navigation scenarios
  • Async client component integration

Code Quality

  • Proper TypeScript usage with well-defined fixture types
  • Clear test names and parameterized tests for reusability
  • Redis service properly configured in CircleCI

Areas for Improvement

1. Missing PR Description

The PR body still contains template placeholders. Please update to include:

  • Why the old tests were flaky
  • How Playwright tests solve the flakiness
  • Brief overview of the new testing approach

2. Missing CHANGELOG Entry

Per CLAUDE.md, add a CHANGELOG entry at the top of the file.

3. Potential Race Condition

pages_controller.rb:72-99 - redis_receiver starts streaming immediately after starting thread. Add comment explaining if intentional.

4. Hardcoded Test Data

Magic number 5 and [0,1,2,3,4] hardcoded in multiple places. Extract to TEST_ITEM_COUNT constant.

5. Missing Error Handling

Add test case for Redis failure scenarios to verify error boundary behavior.

6. TypeScript Configuration

tsconfig.json uses allowDefaultProject workaround. Consider separate tsconfig in e2e-tests directory.

7. Test Coverage Comparison

Removed 421 lines from integration_spec.rb. Please confirm all coverage is maintained in new tests.

8. Multiple Browser Testing

Running on Chromium, Firefox, AND WebKit triples CI time. Consider Chromium-only for faster feedback.

Security

No concerns - proper UUID usage, namespaced Redis streams, cleanup prevents leaks.

Before Merging

  • Update PR description
  • Add CHANGELOG entry
  • Run bundle exec rubocop
  • Verify trailing newlines
  • Document test coverage comparison

Assessment

Code Quality: 4.5/5
Test Coverage: 5/5
Documentation: 2/5

Excellent test architecture! The fixture-based approach is exemplary. Main issues are process-related (CHANGELOG, PR description) rather than code quality. Ready to merge once documentation is complete.

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: 2

♻️ Duplicate comments (2)
react_on_rails_pro/spec/dummy/e2e-tests/fixture.ts (2)

77-86: Unsupported Playwright API: replace page.consoleMessages() and make it robust to messages logged before awaiting

page.consoleMessages() doesn’t exist. Also, if the message was logged before awaiting, the current approach misses it. Capture console history via a one‑time listener and consult it before waiting.

-  waitForConsoleMessage: async ({ page }, use) => {
-    await use(async (msg) => {
-      if ((await page.consoleMessages()).find((consoleMsg) => consoleMsg.text().includes(msg))) {
-        return;
-      }
-
-      await page.waitForEvent('console', {
-        predicate: (consoleMsg) => consoleMsg.text().includes(msg),
-      });
-    });
-  },
+  // Auto-captured console history (see fixture added below)
+  waitForConsoleMessage: async ({ page, consoleHistory }, use) => {
+    await use(async (msg) => {
+      if (consoleHistory.some((t) => t.includes(msg))) return;
+      await page.waitForEvent('console', { predicate: (c) => c.text().includes(msg) });
+    });
+  },

Add these auto fixtures to capture history early:

 const redisControlledTest = base.extend<RedisRequestIdFixture, RedisClientFixture>({
@@
   },
 
+  // Capture console history early to avoid missing already-logged messages
+  consoleHistory: [
+    async ({ page }, use) => {
+      const history: string[] = [];
+      const onConsole = (m: any) => history.push(m.text());
+      page.on('console', onConsole);
+      try {
+        await use(history);
+      } finally {
+        page.off('console', onConsole);
+      }
+    },
+    { auto: true },
+  ],

And extend the type:

 type RedisRequestIdFixture = {
   redisRequestId: string;
   nonBlockingNavigateWithRequestId: (path: string) => Promise<Response | null>;
+  consoleHistory: string[];
 };

88-92: Unsupported Playwright API and possible false negatives: replace page.requests() and start capture before navigation

page.requests() doesn’t exist. Use a request listener and ensure it’s attached before page navigation to avoid missing SSR requests.

-  getNetworkRequests: async ({ page }, use) => {
-    await use(async (requestUrlPattern) => {
-      return (await page.requests()).filter((request) => request.url().match(requestUrlPattern));
-    });
-  },
+  getNetworkRequests: async ({ requestsHistory }, use) => {
+    await use(async (requestUrlPattern) =>
+      requestsHistory.filter((r) => requestUrlPattern.test(r.url())),
+    );
+  },

Add an auto fixture to record requests:

 const redisControlledTest = base.extend<RedisRequestIdFixture, RedisClientFixture>({
@@
   },
 
+  // Capture all network requests from the start of the test
+  requestsHistory: [
+    async ({ page }, use) => {
+      const captured: Request[] = [];
+      const onReq = (r: Request) => captured.push(r);
+      page.on('request', onReq);
+      try {
+        await use(captured);
+      } finally {
+        page.off('request', onReq);
+      }
+    },
+    { auto: true },
+  ],

Update type to include the history fixture:

 type RedisRequestIdFixture = {
   redisRequestId: string;
   nonBlockingNavigateWithRequestId: (path: string) => Promise<Response | null>;
+  requestsHistory: Request[];
 };
🧹 Nitpick comments (4)
react_on_rails_pro/spec/dummy/client/app/components/ServerComponentRouter.tsx (1)

77-80: Optional: DRY the path to avoid drift

Path literal appears in both Link and Route. Consider extracting a local const to keep them in sync.

 export default function App({ basePath = '/server_router', ...props }: { basePath?: string }) {
   return (
     <ErrorBoundary>
+      {/*
+        Keep Link and Route in sync
+      */}
+      const redisReceiverPath = `${basePath}/redis-receiver-for-testing`;
       <nav>
         <ul style={{ listStyle: 'none', padding: 0, margin: 0 }}>
           <li>
-            <Link to={`${basePath}/redis-receiver-for-testing`}>Redis Receiver For Testing</Link>
+            <Link to={redisReceiverPath}>Redis Receiver For Testing</Link>
           </li>
...
-          <Route
-            path={`${basePath}/redis-receiver-for-testing`}
-            element={<RSCRoute componentName="RedisReceiver" componentProps={props} />}
-          />
+          <Route path={redisReceiverPath} element={<RSCRoute componentName="RedisReceiver" componentProps={props} />} />
react_on_rails_pro/spec/dummy/e2e-tests/streaming.spec.ts (2)

26-27: Avoid shadowing Playwright’s test

Rename the tuple element to reduce confusion and potential lint noise.

-).forEach(([pageName, test]) => {
-  test(`incremental rendering of page: ${pageName}`, async ({ matchPageSnapshot, sendRedisItemValue }) => {
+).forEach(([pageName, pageTest]) => {
+  pageTest(`incremental rendering of page: ${pageName}`, async ({ matchPageSnapshot, sendRedisItemValue }) => {
...
-  test(`early hydration of page: ${pageName}`, async ({
+  pageTest(`early hydration of page: ${pageName}`, async ({

82-87: Network request assertions may be timing‑sensitive

If flakes persist, switch to polling to wait until the request set stabilizes.

-    await expect(await getNetworkRequests(/rsc_payload/)).toHaveLength(0);
+    await expect.poll(() => getNetworkRequests(/rsc_payload/)).toHaveLength(0);

-    await expect(await getNetworkRequests(/rsc_payload/)).toHaveLength(1);
+    await expect.poll(() => getNetworkRequests(/rsc_payload/)).toHaveLength(1);

-    await expect(await getNetworkRequests(/rsc_payload/)).toHaveLength(1);
+    await expect.poll(() => getNetworkRequests(/rsc_payload/)).toHaveLength(1);

Also applies to: 89-94, 96-105

react_on_rails_pro/spec/dummy/e2e-tests/fixture.ts (1)

49-55: Optional: add a timeout to nonBlocking navigation

Prevent tests from hanging indefinitely on navigation.

-      return page.goto(fullPath, { waitUntil: 'commit' });
+      return page.goto(fullPath, { waitUntil: 'commit', timeout: 15000 });
📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 182e040 and 916f7c8.

📒 Files selected for processing (8)
  • packages/react-on-rails-pro/src/RSCRoute.tsx (1 hunks)
  • packages/react-on-rails-pro/tsconfig.json (1 hunks)
  • react_on_rails_pro/spec/dummy/client/app/components/RSCPostsPage/ToggleContainer.jsx (1 hunks)
  • react_on_rails_pro/spec/dummy/client/app/components/ServerComponentRouter.tsx (2 hunks)
  • react_on_rails_pro/spec/dummy/client/app/ror-auto-load-components/RedisReceiver.jsx (1 hunks)
  • react_on_rails_pro/spec/dummy/e2e-tests/fixture.ts (1 hunks)
  • react_on_rails_pro/spec/dummy/e2e-tests/streaming.spec.ts (1 hunks)
  • react_on_rails_pro/spec/dummy/playwright.config.ts (1 hunks)
🚧 Files skipped from review as they are similar to previous changes (3)
  • react_on_rails_pro/spec/dummy/playwright.config.ts
  • react_on_rails_pro/spec/dummy/client/app/components/RSCPostsPage/ToggleContainer.jsx
  • react_on_rails_pro/spec/dummy/client/app/ror-auto-load-components/RedisReceiver.jsx
🧰 Additional context used
📓 Path-based instructions (2)
**/*.{js,jsx,ts,tsx,css,scss,json,yml,yaml,md}

📄 CodeRabbit inference engine (CLAUDE.md)

Prettier is the sole authority for formatting all non-Ruby files; never manually format them

Files:

  • packages/react-on-rails-pro/src/RSCRoute.tsx
  • packages/react-on-rails-pro/tsconfig.json
  • react_on_rails_pro/spec/dummy/client/app/components/ServerComponentRouter.tsx
  • react_on_rails_pro/spec/dummy/e2e-tests/fixture.ts
  • react_on_rails_pro/spec/dummy/e2e-tests/streaming.spec.ts
**/*.{js,jsx,ts,tsx}

📄 CodeRabbit inference engine (CLAUDE.md)

Use ESLint for JS/TS code (lint via rake lint or yarn lint)

Files:

  • packages/react-on-rails-pro/src/RSCRoute.tsx
  • react_on_rails_pro/spec/dummy/client/app/components/ServerComponentRouter.tsx
  • react_on_rails_pro/spec/dummy/e2e-tests/fixture.ts
  • react_on_rails_pro/spec/dummy/e2e-tests/streaming.spec.ts
🧠 Learnings (3)
📚 Learning: 2025-06-09T07:58:02.646Z
Learnt from: AbanoubGhadban
PR: shakacode/react_on_rails#1696
File: node_package/src/RSCPayloadGenerator.ts:124-168
Timestamp: 2025-06-09T07:58:02.646Z
Learning: In React Server Components (RSC) implementations, explicit error handling in RSC payload generation streams (like in RSCPayloadGenerator.ts) is not needed because errors will propagate to client components that fetch the RSC payloads (such as RSCRoute components). React's built-in error handling mechanisms and ErrorBoundary components are sufficient for handling these errors at the component level, which allows for better user experience with appropriate error UI fallbacks.

Applied to files:

  • packages/react-on-rails-pro/src/RSCRoute.tsx
📚 Learning: 2025-06-11T12:34:58.182Z
Learnt from: AbanoubGhadban
PR: shakacode/react_on_rails#1696
File: node_package/src/RSCProvider.tsx:0-0
Timestamp: 2025-06-11T12:34:58.182Z
Learning: `RSCProvider` intentionally keeps failed `getServerComponent` promises cached (even if rejected) to avoid repeated fetch attempts; callers must use `refetchComponent` to retry.

Applied to files:

  • packages/react-on-rails-pro/src/RSCRoute.tsx
📚 Learning: 2025-02-13T19:09:15.991Z
Learnt from: AbanoubGhadban
PR: shakacode/react_on_rails#1644
File: node_package/src/RSCWebpackLoader.ts:0-0
Timestamp: 2025-02-13T19:09:15.991Z
Learning: In React Server Components webpack loader, using `new Function('return import("react-server-dom-webpack/node-loader")')()` is necessary as a workaround to bypass TypeScript compilation issues with direct dynamic imports.

Applied to files:

  • packages/react-on-rails-pro/src/RSCRoute.tsx
🧬 Code graph analysis (2)
react_on_rails_pro/spec/dummy/e2e-tests/fixture.ts (1)
react_on_rails_pro/spec/dummy/client/app/ror-auto-load-components/RedisReceiver.jsx (1)
  • value (7-7)
react_on_rails_pro/spec/dummy/e2e-tests/streaming.spec.ts (1)
react_on_rails_pro/spec/dummy/e2e-tests/fixture.ts (5)
  • redisReceiverPageTest (149-149)
  • redisReceiverInsideRouterPageTest (150-150)
  • redisReceiverPageAfterNavigationTest (151-151)
  • redisReceiverPageWithAsyncClientComponentTest (152-152)
  • redisReceiverPageController (148-148)
🪛 Biome (2.1.2)
react_on_rails_pro/spec/dummy/e2e-tests/fixture.ts

[error] 28-28: Unexpected empty object pattern.

(lint/correctness/noEmptyPattern)

⏰ 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: dummy-app-integration-tests (3.4, 22)
  • GitHub Check: dummy-app-integration-tests (3.2, 20)
  • GitHub Check: build
  • GitHub Check: rspec-package-tests (3.2, minimum)
  • GitHub Check: rspec-package-tests (3.4, latest)
  • GitHub Check: rspec-package-tests (3.4, minimum)
  • GitHub Check: rspec-package-tests (3.2, latest)
  • GitHub Check: claude-review
  • GitHub Check: build-and-test
🔇 Additional comments (3)
packages/react-on-rails-pro/tsconfig.json (1)

7-7: File not found in repository.

The file packages/react-on-rails-pro/tsconfig.json does not exist at the specified path. The review comment cannot be verified against actual file content.

Additionally, allowDefaultProject is not a tsconfig.json option—it's an ESLint/typescript-eslint parser option for the ProjectService, not a TypeScript compiler configuration field. If this code change exists elsewhere, ensure allowDefaultProject is placed in ESLint configuration (parserOptions.projectService) rather than in tsconfig.json.

Please confirm:

  • Is the file path correct?
  • Has this file been moved or deleted in this PR?
  • Should the review comment target a different configuration file (e.g., .eslintrc, eslint.config.js)?
packages/react-on-rails-pro/src/RSCRoute.tsx (1)

17-18: LGTM! The "use client" directive correctly marks RSCRoute as a client component.

This change makes the client boundary explicit, which is correct since RSCRoute uses client-side hooks (useRSC) and maintains state (error boundary). The explicit directive helps bundlers and the React compiler consistently treat this as a client component, which may address the flaky test behavior mentioned in the PR objectives by ensuring deterministic client/server boundary detection during SSR, hydration, and client-side navigation.

react_on_rails_pro/spec/dummy/client/app/components/ServerComponentRouter.tsx (1)

44-46: LGTM: link matches route and test selector

The nav entry is correct and matches the test click text. No issues.

Comment on lines +28 to +35
async ({}, use, workerInfo) => {
console.log(`Creating Redis Client at Worker ${workerInfo.workerIndex}`);
const url = process.env.REDIS_URL || 'redis://localhost:6379';
const client = createClient({ url });
await client.connect();
await use(client as RedisClientType);
await client.quit();
},
Copy link
Contributor

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟠 Major

Fix Biome error and harden Redis client lifecycle

  • Replace empty object pattern (Biome error).
  • Ensure teardown runs even on failures, prefer quit() with disconnect() fallback, and attach an error handler.
-  redisClient: [
-    async ({}, use, workerInfo) => {
+  redisClient: [
+    async (_testInfo, use, workerInfo) => {
       console.log(`Creating Redis Client at Worker ${workerInfo.workerIndex}`);
       const url = process.env.REDIS_URL || 'redis://localhost:6379';
       const client = createClient({ url });
-      await client.connect();
-      await use(client as RedisClientType);
-      await client.quit();
+      client.on('error', (err) => console.error('Redis client error in tests:', err));
+      await client.connect();
+      try {
+        await use(client as RedisClientType);
+      } finally {
+        try {
+          await client.quit();
+        } catch {
+          await client.disconnect();
+        }
+      }
     },
     { scope: 'worker' },
   ],
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
async ({}, use, workerInfo) => {
console.log(`Creating Redis Client at Worker ${workerInfo.workerIndex}`);
const url = process.env.REDIS_URL || 'redis://localhost:6379';
const client = createClient({ url });
await client.connect();
await use(client as RedisClientType);
await client.quit();
},
async (_testInfo, use, workerInfo) => {
console.log(`Creating Redis Client at Worker ${workerInfo.workerIndex}`);
const url = process.env.REDIS_URL || 'redis://localhost:6379';
const client = createClient({ url });
client.on('error', (err) => console.error('Redis client error in tests:', err));
await client.connect();
try {
await use(client as RedisClientType);
} finally {
try {
await client.quit();
} catch {
await client.disconnect();
}
}
},
🧰 Tools
🪛 Biome (2.1.2)

[error] 28-28: Unexpected empty object pattern.

(lint/correctness/noEmptyPattern)

Comment on lines +95 to +104
const redisReceiverPageTest = redisReceiverPageController.extend<RedisReceiverPageFixture>({
pagePath: [
async ({ nonBlockingNavigateWithRequestId }, use) => {
const pagePath = '/redis_receiver_for_testing';
await nonBlockingNavigateWithRequestId(pagePath);
await use(pagePath);
},
{ auto: true },
],
});
Copy link
Contributor

Choose a reason for hiding this comment

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

🛠️ Refactor suggestion | 🟠 Major

Ensure listeners attach before navigation to avoid missing events

Force initialization order by depending on the auto fixtures (even if unused).

-    async ({ nonBlockingNavigateWithRequestId }, use) => {
+    async ({ nonBlockingNavigateWithRequestId, consoleHistory: _ch, requestsHistory: _rh }, use) => {
       const pagePath = '/redis_receiver_for_testing';
       await nonBlockingNavigateWithRequestId(pagePath);
       await use(pagePath);
     },
@@
-      async ({ page, nonBlockingNavigateWithRequestId, sendRedisValue }, use) => {
+      async ({ page, nonBlockingNavigateWithRequestId, sendRedisValue, consoleHistory: _ch, requestsHistory: _rh }, use) => {
         const pagePath = '/redis_receiver_for_testing?async_toggle_container=true';
         await nonBlockingNavigateWithRequestId(pagePath);
@@
-    async ({ nonBlockingNavigateWithRequestId }, use) => {
+    async ({ nonBlockingNavigateWithRequestId, consoleHistory: _ch, requestsHistory: _rh }, use) => {
       const pagePath = '/server_router/redis-receiver-for-testing';
       await nonBlockingNavigateWithRequestId(pagePath);
       await use(pagePath);
     },
@@
-    async ({ nonBlockingNavigateWithRequestId, page }, use) => {
+    async ({ nonBlockingNavigateWithRequestId, page, consoleHistory: _ch, requestsHistory: _rh }, use) => {
       await nonBlockingNavigateWithRequestId('/server_router/simple-server-component');
       await expect(page.getByText('Post 1')).toBeVisible({ timeout: 3000 });
       await page.getByText('Redis Receiver For Testing').click();
       await use('/server_router/redis-receiver-for-testing');
     },

Also applies to: 106-122, 124-133, 135-145

🤖 Prompt for AI Agents
In react_on_rails_pro/spec/dummy/e2e-tests/fixture.ts around lines 95-104 (also
apply the same fix to 106-122, 124-133, 135-145), the page fixture navigates
before the auto fixtures have been initialized which can miss event listeners;
fix this by making the async fixture depend on the auto fixtures (add them to
the destructured parameter list or include them in the fixture tuple) and
reference them (even as a no-op) before calling nonBlockingNavigateWithRequestId
so the auto fixtures are initialized and listeners are attached prior to
navigation.

@claude
Copy link

claude bot commented Oct 20, 2025

Code Review: Fix streaming flaky tests

Thank you for this PR! This is a significant improvement that replaces flaky integration tests with a comprehensive Playwright E2E test suite. Here's my detailed review:


Strengths

1. Excellent Test Architecture

  • Snapshot-based testing strategy: The ARIA snapshot approach is brilliant for verifying incremental rendering stages without brittle assertions
  • Well-structured fixtures: The fixture hierarchy (redisReceiverPageController → specific page variants) promotes code reuse and maintainability
  • Comprehensive coverage: Tests verify incremental rendering, early hydration, navigation scenarios, and SSR vs. CSR behavior

2. Strong Code Quality

  • TypeScript implementation: redisReceiver.ts is well-typed with clear interfaces
  • Good separation of concerns: Fixture logic separated from test cases
  • Proper resource management: Redis client lifecycle properly managed with worker-scoped fixtures

3. CI/CD Integration

  • Proper CircleCI job configuration with background services (Rails, Node renderer, Redis)
  • Test results and artifacts properly stored

⚠️ Issues & Concerns

1. Critical: Missing PR Description 🚨

The PR template checklist is incomplete:

  • Summary section needs to describe what flaky tests were fixed and why
  • Test coverage checkbox is unchecked (should be checked - new E2E tests added)
  • Documentation checkbox needs attention - consider documenting the new E2E test approach
  • CHANGELOG entry is missing - this is a required item per CLAUDE.md

Action Required: Complete the PR description with proper context and update CHANGELOG.


2. Potential Memory Leak in redisReceiver.ts ⚠️

Location: react_on_rails_pro/spec/dummy/client/app/utils/redisReceiver.ts:236-240

} finally {
  void readStream();  // Recursive call in finally block
}

Issue: The recursive readStream() call in the finally block could lead to memory issues:

  • Infinite recursion without proper stack cleanup
  • No explicit error handling for catastrophic failures
  • Stream doesn't stop even when isActive = false due to timing issues

Recommendation: Use a while loop instead:

async function readStream() {
  while (isActive && !isEnded) {
    try {
      const results = await client.xRead(/* ... */);
      // ... process results
    } catch (error) {
      console.error('Error reading from stream:', error);
      if (!isActive) break;  // Exit on deliberate shutdown
      await new Promise(resolve => setTimeout(resolve, 1000)); // Backoff on error
    }
  }
}

3. Race Condition in Shared Redis Client 🔧

Location: redisReceiver.ts:48-60

async function getRedisClient() {
  if (!sharedRedisClient) {
    sharedRedisClient = createClient({ url });  // Line 51
  }
  if (!isClientConnected) {
    await sharedRedisClient.connect();  // Line 55 - potential null reference
    isClientConnected = true;
  }
  return sharedRedisClient;
}

Issues:

  1. No mutex/lock protection - concurrent calls could create multiple clients
  2. Between lines 51 and 55, another caller could set isClientConnected = true while connection is still pending
  3. Potential null reference if client creation fails

Recommendation:

let connectionPromise: Promise<RedisClientType> | null = null;

async function getRedisClient() {
  if (!sharedRedisClient) {
    if (!connectionPromise) {
      connectionPromise = (async () => {
        const url = process.env.REDIS_URL || 'redis://localhost:6379';
        const client = createClient({ url });
        await client.connect();
        sharedRedisClient = client;
        isClientConnected = true;
        return client;
      })();
    }
    return connectionPromise;
  }
  return sharedRedisClient;
}

4. Incomplete Resource Cleanup 🧹

Location: redisReceiver.ts:314-334

Issues:

  1. pendingPromises is module-scoped but only cleaned for the specific requestId - could accumulate across requests
  2. No cleanup of pendingPromises entries after they're rejected/resolved
  3. Stream deletion happens in fixture teardown, but the listener doesn't know if stream was deleted

Recommendation: Add proper cleanup:

close: async () => {
  isActive = false;
  activeListeners[requestId] = undefined;
  
  // Clean up ALL pending promises for this listener
  Object.keys(pendingPromises).forEach(key => {
    if (pendingPromises[key]) {
      clearTimeout(pendingPromises[key].timer);
      pendingPromises[key].reject(new Error('Redis connection closed'));
      delete pendingPromises[key];  // Use delete instead of = undefined
    }
  });
  
  // ... rest of cleanup
}

5. Security: Request ID Validation Missing 🔒

Location: react_on_rails_pro/spec/dummy/app/controllers/pages_controller.rb:104

def redis_receiver_for_testing
  @request_id = params[:request_id]
  raise "request_id is required at the url" if @request_id.blank?
  # No validation of request_id format!
end

Issue: Unsanitized request_id is used directly in Redis stream key construction, potentially allowing Redis key injection.

Recommendation:

def redis_receiver_for_testing
  @request_id = params[:request_id]
  raise "request_id is required" if @request_id.blank?
  raise "Invalid request_id format" unless @request_id.match?(/\A[a-f0-9-]{36}\z/i)  # UUID validation
  stream_view_containing_react_components(template: "/pages/redis_receiver")
end

6. Code Style: Missing 'use client' Directive Justification 📝

Location: packages/react-on-rails-pro/src/RSCRoute.tsx:17

'use client';

Question: This appears to be a React Server Component that now has 'use client' directive added. Was this intentional? If so, consider adding a comment explaining why this component needs to be a client component despite being in the RSC context.


7. TypeScript Configuration Anomaly ⚙️

Location: packages/react-on-rails-pro/tsconfig.json:6

"allowDefaultProject": "../spec/dummy/e2e-tests/*"

Issue: This seems unusual - typically test files should have their own tsconfig.json that extends the main one, rather than being allowed in the default project.

Recommendation: Create spec/dummy/e2e-tests/tsconfig.json:

{
  "extends": "../../../tsconfig.json",
  "compilerOptions": {
    "outDir": "./dist"
  },
  "include": ["./**/*"]
}

8. Thread Safety Issue in Rails Controller 🔧

Location: react_on_rails_pro/spec/dummy/app/controllers/pages_controller.rb:72-96

def redis_receiver
  @request_id = SecureRandom.uuid
  redis_thread = Thread.new do
    # ... Redis operations
  end
  stream_view_containing_react_components(template: "/pages/redis_receiver")
  return if redis_thread.join(10)
  Rails.logger.error "Redis thread timed out"
  raise "Redis thread timed out"
end

Issues:

  1. Thread lifecycle not properly managed if exception occurs before join
  2. Thread could be orphaned if controller action raises before join
  3. No guarantee thread is cleaned up on timeout

Recommendation: Use ensure block:

def redis_receiver
  @request_id = SecureRandom.uuid
  redis_thread = nil
  
  begin
    redis_thread = Thread.new do
      # ... existing code
    end
    
    stream_view_containing_react_components(template: "/pages/redis_receiver")
    
    unless redis_thread.join(10)
      Rails.logger.error "Redis thread timed out"
      raise "Redis thread timed out"
    end
  ensure
    redis_thread&.kill if redis_thread&.alive?
  end
end

📊 Performance Considerations

  1. Polling Interval: Redis stream reads use 1-second blocking (BLOCK: 1000). Consider making this configurable for different environments.

  2. Timeout Duration: 8-second timeout for getValue might be too long for fast tests. Consider environment-based configuration:

    const TIMEOUT_MS = process.env.NODE_ENV === 'test' ? 5000 : 8000;
  3. CI Workers: Good choice setting workers: 1 in CI to avoid flakiness. Consider documenting why.


🧪 Test Coverage Assessment

Excellent coverage including:

  • ✅ Incremental rendering stages (5 stages with snapshots)
  • ✅ Early hydration scenarios
  • ✅ Navigation between routes
  • ✅ SSR vs CSR payload behavior
  • ✅ Async client component rendering

Missing coverage:

  • ❌ Error scenarios (network failures, Redis connection errors)
  • ❌ Timeout handling
  • ❌ Stream end message handling
  • ❌ Concurrent getValue calls for same key

Recommendation: Add negative test cases:

test('handles Redis connection failure gracefully', async ({ page, sendRedisItemValue }) => {
  // Simulate Redis failure
  // Verify error boundaries catch the error
});

📋 Before Merge Checklist

  • Complete PR description with context about flaky tests fixed
  • Add CHANGELOG entry
  • Fix memory leak in readStream (use while loop)
  • Fix race condition in getRedisClient
  • Add request_id validation in controller
  • Improve resource cleanup in close()
  • Run bundle exec rubocop and fix any violations (per CLAUDE.md)
  • Ensure all files end with newline character (per CLAUDE.md)
  • Consider adding error scenario tests
  • Address or document the 'use client' directive change

🎯 Summary

This is high-quality work that significantly improves test reliability. The Playwright integration and snapshot-based testing approach are excellent choices. However, there are some critical issues around resource management and potential race conditions that should be addressed before merge.

The most urgent items are:

  1. Complete the PR description and add CHANGELOG entry
  2. Fix the memory leak in recursive readStream
  3. Add request_id validation for security
  4. Fix race condition in Redis client initialization

Once these are addressed, this will be a solid contribution! 🚀


Rating: Approve with changes requested ⚠️

🤖 Generated with Claude Code

@AbanoubGhadban AbanoubGhadban changed the title Fix streaming flaky tests Replace streaming flaky tests with Playwright E2E tests Oct 21, 2025
@claude
Copy link

claude bot commented Oct 21, 2025

Pull Request Review: Replace streaming flaky tests with Playwright E2E tests

Thank you for this comprehensive PR! The move from flaky integration tests to Playwright E2E tests is a great improvement. Here's my detailed review:


✅ Strengths

1. Excellent Test Architecture

  • The fixture-based approach in e2e-tests/fixture.ts is well-designed and highly reusable
  • Worker-scoped Redis client with proper cleanup is an excellent pattern
  • Multiple test fixtures for different scenarios (router navigation, async components) shows good coverage
  • ARIA snapshot testing is a smart approach for verifying incremental rendering behavior

2. Good Code Organization

  • Clean separation between test fixtures and actual test specs
  • RedisReceiver component is well-structured with proper error boundaries
  • CI configuration properly integrates the new E2E test suite

3. Strong Test Coverage

  • Tests cover multiple scenarios: incremental rendering, early hydration, SSR vs CSR, navigation
  • Out-of-order Redis value delivery is tested (Items 0, 3, 1, 2, 4) - excellent for streaming validation
  • Network request validation ensures RSC payload behavior is correct

⚠️ Issues & Concerns

1. Missing PR Checklist Items
The PR description shows unchecked items:

  • Add/update test to cover these changes ✅ (Done, but not checked)
  • Update documentation
  • Update CHANGELOG file

Per CLAUDE.md requirements, you must update the CHANGELOG and check all applicable items.

2. Potential Race Condition in Controller
react_on_rails_pro/spec/dummy/app/controllers/pages_controller.rb:95-98

return if redis_thread.join(10)

Rails.logger.error "Redis thread timed out"
raise "Redis thread timed out"

The redis_thread.join(10) doesn't guarantee the thread won't leak if it times out. Consider explicitly killing the thread or using a more robust cleanup mechanism.

3. Hard-coded Timeout Value
react_on_rails_pro/spec/dummy/client/app/utils/redisReceiver.ts:297

const REDIS_READ_TIMEOUT = 10000;

This timeout is hard-coded in multiple places (10 seconds in controller, 10000ms in TypeScript). Consider making this a configuration constant to ensure consistency.

4. Missing Error Handling Context
e2e-tests/fixture.ts:31-34

const client = createClient({ url });
await client.connect();
await use(client as RedisClientType);
await client.quit();

If client.connect() fails, the test setup will fail silently. Consider adding try-catch with meaningful error messages.

5. TypeScript Config Modification Needs Clarification
packages/react-on-rails-pro/tsconfig.json:6

"allowDefaultProject": "../spec/dummy/e2e-tests/*"

This setting seems unusual and isn't documented. Why is this needed? Consider adding a comment explaining its purpose.

6. 'use client' Directive Added
packages/react-on-rails-pro/src/RSCRoute.tsx:17

'use client';

This change wasn't mentioned in the PR description. Was this necessary for the tests, or is it addressing a separate issue? If separate, it should be its own commit or documented.


🔍 Best Practices & Suggestions

1. Snapshot File Maintenance
You've added 6 ARIA snapshot files. Consider adding a note in the test file about when these snapshots should be updated vs when changes indicate a real issue.

2. Browser Test Configuration
The Playwright config tests against Chromium, Firefox, and WebKit. On CI with workers: 1, this could be slow. Consider:

  • Running only Chromium in CI by default
  • Running all browsers in a nightly/weekly job
  • Or document why all three browsers are necessary for streaming tests

3. Test Naming Consistency
The test uses string interpolation for names: incremental rendering of page: ${pageName}. Good approach! But ensure all test names follow this pattern consistently.

4. Missing Negative Test Cases
Consider adding tests for:

  • Redis connection failures
  • Malformed data in Redis streams
  • Timeout scenarios
  • What happens when Redis is unavailable

5. Fixture Cleanup Documentation
The fixture cleanup at e2e-tests/fixture.ts:44-45 is excellent, but consider adding a comment explaining why stream cleanup is important (prevents test pollution).


🚨 Action Items Before Merge

MANDATORY (per CLAUDE.md):

  1. ✅ Fill out the PR description with actual summary of changes
  2. ✅ Check the completed items in the PR checklist
  3. ✅ Add CHANGELOG entry at the top of the file
  4. ✅ Ensure all files end with a newline character
  5. ✅ Run bundle exec rubocop and fix ALL violations
  6. ✅ Explain the 'use client' change in RSCRoute.tsx

RECOMMENDED:

  1. Add error handling to Redis client connection in fixture
  2. Extract timeout constants to shared configuration
  3. Add test documentation about ARIA snapshot updates
  4. Consider adding negative test cases for error scenarios
  5. Document the TypeScript allowDefaultProject setting

📊 Test Quality Assessment

Coverage: ⭐⭐⭐⭐☆ (4/5)

  • Great coverage of happy paths and streaming scenarios
  • Could use more error/failure case testing

Maintainability: ⭐⭐⭐⭐⭐ (5/5)

  • Excellent fixture reuse pattern
  • Clear test organization
  • Good use of helpers

Reliability: ⭐⭐⭐⭐☆ (4/5)

  • Much better than the flaky tests being replaced
  • Some timeout/race condition concerns in controller code

🎯 Conclusion

This is a solid PR that significantly improves test reliability. The Playwright E2E approach is the right solution for streaming SSR tests. The main blockers are completing the PR checklist items per CLAUDE.md requirements. Once those are addressed, this will be ready to merge.

Great work on the comprehensive test fixtures and ARIA snapshot approach! 🚀

@claude
Copy link

claude bot commented Oct 21, 2025

PR Review: Replace streaming flaky tests with Playwright E2E tests

Great work on migrating from flaky integration tests to Playwright E2E tests! This is a solid improvement for test reliability. Here's my detailed review:


Strengths

  1. Well-structured Playwright fixtures - The fixture composition pattern in fixture.ts is excellent, with proper separation of concerns (Redis client, request ID management, page controllers).

  2. Comprehensive test coverage - Tests cover multiple scenarios:

    • Incremental rendering across different page contexts
    • Early hydration behavior
    • SSR vs CSR payload requests
    • Navigation scenarios
  3. Good use of snapshot testing - ARIA snapshots are appropriate for this use case, providing stable assertions for incremental streaming behavior.

  4. Proper CI integration - CircleCI job setup looks good with Redis service, proper caching, and sequential dependency management.

  5. Resource cleanup - Redis streams are properly cleaned up in fixtures with try/finally blocks.


⚠️ Issues & Recommendations

Critical: Missing Newlines

Several new files are missing trailing newlines, which will cause CI failures per CLAUDE.md requirements:

  • react_on_rails_pro/spec/dummy/e2e-tests/streaming.spec.ts:106 (missing newline)
  • react_on_rails_pro/spec/dummy/e2e-tests/fixture.ts:154 (missing newline)
  • react_on_rails_pro/spec/dummy/client/app/ror-auto-load-components/RedisReceiver.jsx:64 (missing newline)
  • react_on_rails_pro/spec/dummy/playwright.config.ts:85 (missing newline)
  • All ARIA snapshot files in __snapshots__/streaming.spec.ts/ (stage0-5.aria.yml)

Action Required: Run rake autofix to fix formatting issues before committing.

Code Quality

  1. Hardcoded values in tests (streaming.spec.ts:27-43, 65-77):

    await sendRedisItemValue(0, 'Incremental Value1');
    await sendRedisItemValue(3, 'Incremental Value4');

    Consider extracting test data into constants for better maintainability:

    const TEST_VALUES = ['Value1', 'Value2', 'Value3', 'Value4', 'Value5'];
    const TEST_STAGES = [0, 3, 1, 2, 4]; // order of item delivery
  2. Magic numbers (RedisReceiver.jsx:52):

    {[0, 1, 2, 3, 4].map((index) => (

    Should use a named constant:

    const REDIS_ITEMS_COUNT = 5;
    {Array.from({ length: REDIS_ITEMS_COUNT }, (_, index) => (
  3. Inconsistent timeout values:

    • fixture.ts uses 3000ms timeout at line 139
    • redisReceiver.ts changed from 8000ms to 10000ms (line 297)
    • pages_controller.rb uses 10s timeout at line 95

    Consider centralizing timeout configuration.

  4. Error handling in controller (pages_controller.rb:72-99):
    The redis_receiver method creates a thread but the timeout logic could be clearer. If the thread times out, the error is logged but the thread is abandoned. Consider:

    unless redis_thread.join(10)
      redis_thread.kill # Clean up the thread
      Rails.logger.error "Redis thread timed out"
      raise "Redis thread timed out"
    end

Security Concerns

  1. request_id validation (pages_controller.rb:102):

    @request_id = params[:request_id]
    raise "request_id is required at the url" if @request_id.blank?

    The request_id should be validated to ensure it's a valid UUID format to prevent Redis key injection:

    @request_id = params[:request_id]
    raise "Invalid request_id format" unless @request_id&.match?(/\A[0-9a-f-]{36}\z/i)
  2. Redis connection in controller: The redis_receiver method creates a new Redis connection without explicit configuration. Consider using a connection pool or the application's configured Redis instance.

Performance Considerations

  1. Playwright browser matrix (playwright.config.ts:41-55): Running tests on Chromium, Firefox, AND WebKit will triple CI execution time. Given this is testing server-streaming behavior (not browser compatibility), consider:

    • Running only on Chromium in CI
    • Making multi-browser tests optional/manual
    • Using CI matrix to parallelize if kept
  2. CI parallelization (.circleci/config.yml:28):

    workers: process.env.CI ? 1 : undefined

    Running with 1 worker on CI will be slow. Consider increasing if CI resources allow.

Test Coverage Gaps

  1. Missing error scenarios: No tests for:

    • Redis connection failures
    • Timeout scenarios
    • Malformed data in Redis streams
    • Network failures during streaming
  2. Missing edge cases:

    • What happens if Redis values arrive before page loads?
    • What happens if the same item is sent multiple times?
    • Browser refresh during streaming?
  3. Accessibility: While using ARIA snapshots is good, consider adding explicit accessibility tests (keyboard navigation, screen reader announcements).

Documentation

  1. PR Description incomplete: The PR template sections are not filled out:

    • Summary is just placeholder text
    • Test coverage checkbox is unchecked
    • CHANGELOG not updated
    • No benchmark/performance information
  2. Missing test documentation: The e2e-tests/ directory lacks a README explaining:

    • How to run tests locally
    • Redis setup requirements
    • Fixture architecture
    • How to add new tests

Minor Issues

  1. TSConfig change (packages/react-on-rails-pro/tsconfig.json:6):

    "allowDefaultProject": "../spec/dummy/e2e-tests/*"

    This seems like a workaround. Consider moving e2e tests to a properly configured directory or adding a separate tsconfig for tests.

  2. Added 'use client' directive (packages/react-on-rails-pro/src/RSCRoute.tsx:17):

    'use client';

    This seems unrelated to the PR's main purpose. If needed, should be in a separate commit with explanation.

  3. Duplicate TODO comment (.circleci/config.yml:346):

    # TODO: DRY with previous job

    Good intention, but should be addressed or tracked in an issue.


📋 Action Items Before Merge

  1. CRITICAL: Run rake autofix to add trailing newlines to all files
  2. CRITICAL: Validate request_id format to prevent injection
  3. Required: Complete PR description (summary, CHANGELOG entry)
  4. Required: Verify tests pass with all RuboCop rules
  5. Recommended: Add test documentation README
  6. Recommended: Extract magic numbers and test data to constants
  7. Recommended: Consider reducing browser matrix for CI performance
  8. Nice to have: Add error scenario tests

📊 Overall Assessment

This is a valuable improvement that replaces flaky tests with more reliable E2E tests. The Playwright setup is well-architected with good fixture composition. However, there are several issues that must be addressed before merging:

  • Formatting issues (will break CI)
  • Security concern with request_id validation
  • Incomplete PR documentation

Recommendation: Request changes to address critical and required items above.


Review conducted following repository's CLAUDE.md guidelines.

@claude
Copy link

claude bot commented Oct 21, 2025

Code Review: PR #1869 - Replace streaming flaky tests with Playwright E2E tests

Summary

This PR successfully replaces flaky RSpec integration tests with robust Playwright E2E tests for streaming server rendering functionality. The new tests use Redis-backed incremental rendering to verify streaming behavior across multiple scenarios.

✅ Strengths

1. Excellent Test Architecture

  • Well-structured fixtures (fixture.ts:26-56): The use of Playwright's fixture system with worker-scoped Redis clients is elegant and efficient
  • Test composition: Multiple test variants (standard, inside router, after navigation, with async client components) demonstrate comprehensive coverage
  • Smart snapshot testing: Using ARIA snapshots to verify incremental rendering stages is the right approach for this use case

2. Good Code Quality

  • Type safety: Proper TypeScript types for all fixtures
  • Resource cleanup: Redis streams properly cleaned up in fixture.ts:44-46
  • Error handling: Ruby controller has proper error handling with logging (pages_controller.rb:82-90)
  • Reusable test utilities: The fixture system promotes DRY principles

3. CI Integration

  • Proper CircleCI job configuration with artifact storage
  • Parallel test execution disabled on CI (good for stability)
  • HTML and JUnit reporters configured correctly

⚠️ Issues & Recommendations

Critical

1. Security: Missing Input Validation (pages_controller.rb:102-103)

def redis_receiver_for_testing
  @request_id = params[:request_id]
  raise "request_id is required at the url" if @request_id.blank?

Issue: The request_id from params is used directly in Redis stream names without validation. While this is a test endpoint, it could be exploited for Redis key injection.

Recommendation: Add UUID format validation:

def redis_receiver_for_testing
  @request_id = params[:request_id]
  raise "request_id is required" if @request_id.blank?
  raise "Invalid request_id format" unless @request_id.match?(/\A[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}\z/i)
  stream_view_containing_react_components(template: "/pages/redis_receiver")
end

2. Missing 'use client' Directive (RSCRoute.tsx:14-16)

The addition of 'use client'; to RSCRoute.tsx is good, but this should be mentioned in the PR description as it's a functional change beyond just adding tests.

High Priority

3. Test Flakiness Risk: Race Condition (fixture.ts:77-86)

waitForConsoleMessage: async ({ page }, use) => {
  await use(async (msg) => {
    if ((await page.consoleMessages()).find((consoleMsg) => consoleMsg.text().includes(msg))) {
      return;
    }
    await page.waitForEvent('console', {
      predicate: (consoleMsg) => consoleMsg.text().includes(msg),
    });
  });
},

Issue: There's a race condition between checking existing messages and waiting for new ones. A message could arrive between these two calls.

Recommendation: Add a timeout or use a more atomic approach:

waitForConsoleMessage: async ({ page }, use) => {
  await use(async (msg, timeout = 5000) => {
    const existing = (await page.consoleMessages()).find((consoleMsg) => 
      consoleMsg.text().includes(msg)
    );
    if (existing) return;
    
    await page.waitForEvent('console', {
      predicate: (consoleMsg) => consoleMsg.text().includes(msg),
      timeout,
    });
  });
},

4. Hard-coded Magic Numbers (streaming.spec.ts:27-43)

The test uses hard-coded item indices (0, 3, 1, 2, 4) to test out-of-order delivery. While this works, it would be clearer with a comment explaining the intentional ordering.

Recommendation: Add a comment:

// Test out-of-order delivery: 1st, 4th, 2nd, 3rd, 5th
await sendRedisItemValue(0, 'Incremental Value1');
await sendRedisItemValue(3, 'Incremental Value4');
// ... etc

Medium Priority

5. Inconsistent Timeout Values

  • Redis read timeout: 10000ms (redisReceiver.ts:3)
  • Redis thread join timeout: 10s (pages_controller.rb:95)
  • Playwright default timeout: varies

Recommendation: Document why timeouts are set to specific values, and consider making them configurable via environment variables.

6. Missing CHANGELOG Update

Per the PR template checklist and CLAUDE.md guidelines, a CHANGELOG entry is required but not included.

Recommendation: Add an entry to CHANGELOG.md:

### [Unreleased]
#### Tests
- Replace flaky streaming RSpec tests with Playwright E2E tests for better reliability [#1869]

7. Incomplete PR Description

The PR description still contains template placeholders. Please update with:

  • Summary of what changed
  • Why the old tests were flaky
  • How the new approach solves the problem
  • Any breaking changes or functional changes (like the RSCRoute.tsx modification)

Low Priority

8. Code Duplication in Tests (streaming.spec.ts:27-77)

The same sequence of sendRedisItemValue + matchPageSnapshot is duplicated across test cases.

Recommendation: Extract to a helper function:

async function testIncrementalRendering({ matchPageSnapshot, sendRedisItemValue }) {
  const stages = [
    { item: 0, value: 'Incremental Value1', stage: 'stage1' },
    { item: 3, value: 'Incremental Value4', stage: 'stage2' },
    // ... etc
  ];
  
  await matchPageSnapshot('stage0');
  for (const { item, value, stage } of stages) {
    await sendRedisItemValue(item, value);
    await matchPageSnapshot(stage);
  }
}

9. Snapshot File Naming (snapshots)

All snapshot files share the same names across different test scenarios. While Playwright handles this, it could be confusing. Consider scenario-specific snapshots if they differ.

10. ESLint Configuration (eslint.config.mjs:199-215)

Two separate configuration blocks for spec/dummy/e2e-tests/* could be merged:

{
  files: ['spec/dummy/e2e-tests/*'],
  rules: {
    'no-empty-pattern': ['error', { allowObjectPatternsAsParameters: true }],
    'react-hooks/rules-of-hooks': ['off'],
  },
},

🔍 Performance Considerations

Positive

  • Worker-scoped Redis client reduces connection overhead
  • Parallel test execution for better CI performance
  • ARIA snapshots are lightweight compared to full HTML snapshots

Concerns

  • CI execution time: Running tests across 3 browsers (chromium, firefox, webkit) could be slow. Consider:
    • Only running chromium on CI by default
    • Running other browsers on a schedule or manually

Suggestion (playwright.config.ts:41-75):

projects: process.env.CI 
  ? [{ name: 'chromium', use: { ...devices['Desktop Chrome'] } }]
  : [
      { name: 'chromium', use: { ...devices['Desktop Chrome'] } },
      { name: 'firefox', use: { ...devices['Desktop Firefox'] } },
      { name: 'webkit', use: { ...devices['Desktop Safari'] } },
    ],

📊 Test Coverage Assessment

Excellent coverage of:

  • ✅ Incremental rendering in correct order
  • ✅ Out-of-order chunk delivery
  • ✅ Early hydration before streaming completes
  • ✅ Navigation scenarios (direct load, after navigation, inside router)
  • ✅ Async client components during streaming
  • ✅ Network request verification (RSC payload fetching)

Missing coverage (consider adding):

  • ❓ Error scenarios (Redis connection failures, timeout handling)
  • ❓ Concurrent page loads with different request IDs
  • ❓ Browser back/forward navigation behavior
  • ❓ Network failures during streaming

🔒 Security Assessment

Overall: Low risk for production, medium risk for test environments

  1. Redis injection risk in test endpoint (see Critical TODO for first version #1)
  2. Redis runs without authentication in tests - acceptable for test env
  3. No rate limiting on test endpoints - acceptable for test env

📝 Documentation

Missing:

  • No README or documentation explaining how to run E2E tests locally
  • No documentation of the Redis streaming test pattern for future contributors

Recommendation: Add react_on_rails_pro/spec/dummy/e2e-tests/README.md:

# E2E Tests for React on Rails Pro

## Running Tests Locally
\`\`\`bash
# Start Redis
redis-server

# Start Rails server
RAILS_ENV=test rails server

# Start node renderer
yarn run node-renderer

# Run tests
yarn e2e-test
\`\`\`

## Test Architecture
These tests verify streaming server rendering using Redis-backed incremental rendering...

🎯 Overall Assessment

Score: 8/10

This is a well-architected PR that significantly improves test reliability. The use of Playwright with custom fixtures is professional and maintainable. However, it requires:

  1. Must fix: Input validation for request_id (security)
  2. Should fix: Race condition in waitForConsoleMessage, CHANGELOG update, PR description
  3. Nice to have: Documentation, test helper extraction, CI browser optimization

✅ Approval Recommendation

Approve with changes requested. Once the critical security issue and PR description are addressed, this is ready to merge. The improvements to test reliability justify the complexity of the new testing infrastructure.


Review completed by Claude Code AI Assistant

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: 1

📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between b72d198 and 7176d49.

📒 Files selected for processing (4)
  • .circleci/config.yml (2 hunks)
  • react_on_rails_pro/eslint.config.mjs (1 hunks)
  • react_on_rails_pro/spec/dummy/e2e-tests/streaming.spec.ts (1 hunks)
  • react_on_rails_pro/spec/dummy/tsconfig.json (1 hunks)
🚧 Files skipped from review as they are similar to previous changes (2)
  • react_on_rails_pro/spec/dummy/e2e-tests/streaming.spec.ts
  • .circleci/config.yml
🧰 Additional context used
📓 Path-based instructions (1)
**/*.{js,jsx,ts,tsx,css,scss,json,yml,yaml,md}

📄 CodeRabbit inference engine (CLAUDE.md)

Prettier is the sole authority for formatting all non-Ruby files; never manually format them

Files:

  • react_on_rails_pro/spec/dummy/tsconfig.json
⏰ 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). (8)
  • GitHub Check: dummy-app-integration-tests (3.4, 22)
  • GitHub Check: dummy-app-integration-tests (3.2, 20)
  • GitHub Check: rspec-package-tests (3.4, minimum)
  • GitHub Check: rspec-package-tests (3.4, latest)
  • GitHub Check: claude-review
  • GitHub Check: rspec-package-tests (3.2, minimum)
  • GitHub Check: rspec-package-tests (3.2, latest)
  • GitHub Check: build
🔇 Additional comments (2)
react_on_rails_pro/eslint.config.mjs (1)

200-200: The review comment is incorrect; the current glob pattern is appropriate for the codebase.

The e2e-tests/ directory contains only two files at the top level (fixture.ts and streaming.spec.ts) with no nested subdirectories. The current pattern spec/dummy/e2e-tests/* correctly matches both files. The suggested pattern spec/dummy/e2e-tests/**/* is unnecessary and inconsistent with the actual directory structure.

Likely an incorrect or invalid review comment.

react_on_rails_pro/spec/dummy/tsconfig.json (1)

10-16: Verify the e2e-tests glob pattern will match all test files.

The e2e-tests directory doesn't exist yet, but the pattern e2e-tests/* on line 14 only matches top-level files. If E2E tests will be organized in subdirectories, update the pattern to e2e-tests/**/* for recursive matching:

  "include": [
    "client/**/*.ts",
    "client/**/*.tsx",
    "client/**/*.d.ts",
-   "e2e-tests/*",
+   "e2e-tests/**/*",
    "./playwright.config.ts"
  ]

Comment on lines +199 to +216
{
files: ['spec/dummy/e2e-tests/*'],

rules: {
'no-empty-pattern': [
'error',
{
allowObjectPatternsAsParameters: true,
},
],
},
},
{
files: ['spec/dummy/e2e-tests/*'],
rules: {
'react-hooks/rules-of-hooks': ['off'],
},
},
Copy link
Contributor

Choose a reason for hiding this comment

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

🛠️ Refactor suggestion | 🟠 Major

Combine duplicate configuration blocks.

Both blocks target the same file pattern. Merge them into a single configuration to reduce duplication and improve maintainability.

Apply this diff to consolidate the blocks:

  {
    files: ['spec/dummy/e2e-tests/*'],

    rules: {
      'no-empty-pattern': [
        'error',
        {
          allowObjectPatternsAsParameters: true,
        },
      ],
-    },
-  },
-  {
-    files: ['spec/dummy/e2e-tests/*'],
-    rules: {
      'react-hooks/rules-of-hooks': ['off'],
    },
  },
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
{
files: ['spec/dummy/e2e-tests/*'],
rules: {
'no-empty-pattern': [
'error',
{
allowObjectPatternsAsParameters: true,
},
],
},
},
{
files: ['spec/dummy/e2e-tests/*'],
rules: {
'react-hooks/rules-of-hooks': ['off'],
},
},
{
files: ['spec/dummy/e2e-tests/*'],
rules: {
'no-empty-pattern': [
'error',
{
allowObjectPatternsAsParameters: true,
},
],
'react-hooks/rules-of-hooks': ['off'],
},
},
🤖 Prompt for AI Agents
In react_on_rails_pro/eslint.config.mjs around lines 199 to 216, there are two
separate override objects targeting the same files pattern
('spec/dummy/e2e-tests/*'); merge them into a single override object that
contains both rules ('no-empty-pattern' with allowObjectPatternsAsParameters:
true, and 'react-hooks/rules-of-hooks' set to 'off') to remove duplication and
keep configuration consolidated.

@AbanoubGhadban AbanoubGhadban merged commit b73ff63 into master Oct 21, 2025
16 checks passed
@AbanoubGhadban AbanoubGhadban deleted the abanoubghadban/ror1866/fix-streaming-flaky-tests branch October 21, 2025 12:17
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant