-
-
Notifications
You must be signed in to change notification settings - Fork 1.5k
e2e(vue-router): vue-query suites #6083
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Conversation
WalkthroughThis PR adds two complete end-to-end test project scaffolds for TanStack Vue Router integrated with Vue Query and Playwright testing: one using file-based routing and one using traditional lazy routes. Both include full project setup (configs, dependencies, source code) and example posts/post-detail flows with error handling. Changes
Sequence Diagram(s)sequenceDiagram
actor User
participant Browser as Vue App<br/>(Router + Query)
participant Router as TanStack<br/>Router
participant QueryClient as Vue Query<br/>Client
participant API as Dummy Server
participant ErrorHandler as Error<br/>Boundary
User->>Browser: Navigate to /posts/$postId
Browser->>Router: Route match
Router->>QueryClient: loader: ensureQueryData(postQueryOptions(postId))
QueryClient->>QueryClient: Check cache
alt Cache hit
QueryClient-->>Router: Return cached data
else Cache miss
QueryClient->>API: fetchPost(postId)
alt Success (200)
API-->>QueryClient: { id, title, body }
QueryClient-->>Router: Return fetched data
else Not Found (404)
API-->>QueryClient: 404 response
QueryClient->>ErrorHandler: Throw PostNotFoundError
ErrorHandler-->>Browser: Render PostErrorComponent
Note over ErrorHandler: Show retry button
Browser-->>User: Display "Post not found"
end
end
alt Post found
Router->>Browser: Render PostComponent
Browser->>Browser: useQuery(postQueryOptions(postId))
Browser->>Browser: Route.useLoaderData()
Browser-->>User: Display post title & body
end
alt User clicks retry
User->>Browser: Click retry
Browser->>Router: router.invalidate()
Router->>QueryClient: Invalidate query
QueryClient->>API: fetchPost(postId) [again]
end
Estimated code review effort🎯 4 (Complex) | ⏱️ ~45 minutes
Areas requiring extra attention:
Possibly related PRs
Suggested reviewers
Poem
Pre-merge checks and finishing touches❌ Failed checks (1 warning)
✅ Passed checks (2 passed)
✨ Finishing touches
🧪 Generate unit tests (beta)
Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out. Comment |
|
View your CI Pipeline Execution ↗ for commit 72b462b
☁️ Nx Cloud last updated this comment at |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Actionable comments posted: 6
🧹 Nitpick comments (20)
e2e/vue-router/basic-vue-query/src/posts.lazy.tsx (1)
17-17: Consider proper typing instead of type assertion.The type assertion
as Array<{ id: string; title: string }>assumes the data shape. IfpostsQueryOptionsis properly typed with generics, this assertion could be avoided, improving type safety.Example if using typed query options:
// In posts.ts or postsQueryOptions.tsx type Post = { id: string; title: string } export const postsQueryOptions = queryOptions<Post[]>({ queryKey: ['posts'], queryFn: () => fetchPosts(), })Then the type assertion wouldn't be needed:
- ...((posts.value ?? []) as Array<{ id: string; title: string }>), + ...(posts.value ?? []),e2e/vue-router/basic-vue-query-file-based/src/postsQueryOptions.tsx (1)
1-7: Looks good; considerqueryFn: fetchPostsfor brevity (optional).e2e/vue-router/basic-vue-query-file-based/package.json (1)
5-12:test:e2escript is not Windows-friendly (rm -rf,;).If these suites ever need to run on Windows, consider swapping
rm -rffor a portable alternative (e.g.rimraf) and avoid;chaining.e2e/vue-router/basic-vue-query/package.json (1)
5-12:test:e2escript is not Windows-friendly (rm -rf,;).e2e/vue-router/basic-vue-query/tests/app.spec.ts (4)
3-5: Make initial navigation less flaky by waiting on a stable “home loaded” signal.
page.goto('/')alone can race hydration; consider asserting a stable element (e.g.,Welcome Home!) before proceeding.
7-11: Avoid brittle selectors: don’t key the test on a truncated link label and a generic heading.
name: 'sunt aut facere repe'andgetByRole('heading')can break if truncation/markup changes; prefergetByRole('heading', { name: /sunt aut facere/i })or adata-testid.
13-24: Nested layout assertions look good; consider asserting on specific regions to reduce false positives.
All checks are against#app; if content is duplicated elsewhere, this could pass incorrectly—optionally narrow to the layout container if available.
26-33: Not-found flow is solid; consider asserting URL change (optional).
If the router keeps content but not URL, this could miss regressions; optionally assertpage.url()contains the not-found path then returns to/.e2e/vue-router/basic-vue-query-file-based/tests/app.spec.ts (1)
25-51: Same selector brittleness as the other suite (truncated link + generic heading).
Recommend aligning selectors to stable text/regex or test ids to prevent truncation-driven failures.e2e/vue-router/basic-vue-query-file-based/src/routes/posts.tsx (2)
11-42: StringifypostIdparams to satisfy strict typing and avoid subtle route key mismatches.
Ifpost.idis numeric (common for posts APIs), passpostId: String(post.id)to keep params consistently string-typed.
18-36: Test-only “Non-existent Post” is fine, but consider making it explicit why it’s injected.
A short comment helps future readers understand it exists to exercise error/not-found paths.e2e/vue-router/basic-vue-query-file-based/src/main.tsx (1)
29-39: Non-null assertion on#appis OK for e2e, but consider a clearer failure mode (optional).
If the element is missing, this will throw; optionally assert and throw a custom error for easier debugging.e2e/vue-router/basic-vue-query-file-based/src/routes/__root.tsx (2)
49-57: Keep@ts-expect-errorbut make it resilient to refactors.
If route typing changes, this could become a hard error or no longer error; consider adding a short comment explaining it exists to validate not-found navigation.
61-63: Consider gating devtools to reduce CI noise/flakiness (optional).
Devtools can affect layout and sometimes timing; consider showing them only whenimport.meta.env.DEVor a test flag is enabled.e2e/vue-router/basic-vue-query-file-based/playwright.config.ts (1)
27-32:webServer.commandenv var syntax is shell-specific; considercross-envfor portability.
VITE_NODE_ENV="test" ...will fail in some environments (notably Windows cmd/powershell).e2e/vue-router/basic-vue-query-file-based/src/routes/posts.$postId.tsx (2)
16-34: Retry should ideally invalidate just this route (not everything).
router.invalidate()with no args is broad; for e2e it’s probably OK, but it can add noise/flakiness as suites grow. If the API supports it, scope invalidation to this match/route.- router.invalidate() + router.invalidate(/* TODO: scope to /posts/$postId if supported */)
36-47: Prefer a single source of truth for rendering (useQueryor loader data).
Right now you subscribe viauseQuery(...)but render fromRoute.useLoaderData(). If you want query-driven updates, render from the query’sdata(and optionally fall back to loader data).function PostComponent() { const postId = Route.useParams().value.postId - useQuery(postQueryOptions(postId)) - const post = Route.useLoaderData() + const post = Route.useLoaderData() + const { data } = useQuery(postQueryOptions(postId)) return ( <div class="space-y-2"> - <h4 class="text-xl font-bold underline">{post.value.title}</h4> - <div class="text-sm">{post.value.body}</div> + <h4 class="text-xl font-bold underline"> + {(data.value ?? post.value).title} + </h4> + <div class="text-sm">{(data.value ?? post.value).body}</div> </div> ) }e2e/vue-router/basic-vue-query/playwright.config.ts (1)
28-33:webServer.commandenv var syntax is not cross-platform.
If these suites are expected to run on Windows, consider usingcross-env/cross-env-shell, or Playwright’swebServer.env(if available in your Playwright version).webServer: { - command: `VITE_NODE_ENV="test" VITE_EXTERNAL_PORT=${EXTERNAL_PORT} VITE_SERVER_PORT=${PORT} pnpm build && VITE_NODE_ENV="test" VITE_EXTERNAL_PORT=${EXTERNAL_PORT} VITE_SERVER_PORT=${PORT} pnpm preview --port ${PORT}`, + command: `cross-env VITE_NODE_ENV=test VITE_EXTERNAL_PORT=${EXTERNAL_PORT} VITE_SERVER_PORT=${PORT} pnpm build && cross-env VITE_NODE_ENV=test VITE_EXTERNAL_PORT=${EXTERNAL_PORT} VITE_SERVER_PORT=${PORT} pnpm preview --port ${PORT}`, url: baseURL, reuseExistingServer: !process.env.CI, stdout: 'pipe', },e2e/vue-router/basic-vue-query/src/main.tsx (2)
120-138: Retry invalidation is broad; consider scoping if supported.
router.invalidate()without args may invalidate more than needed and slow the suite as more routes are added.
235-246: Router preload staleness comment is good—double-check it aligns with query staleness.
defaultPreloadStaleTime: 0ensures router loaders rerun, but TanStack Query’sstaleTime/refetchOnMountstill controls whether the component observer refetches immediately afterensureQueryData. Worth confirming the combined behavior is what you want for stable e2e runs.
📜 Review details
Configuration used: CodeRabbit UI
Review profile: CHILL
Plan: Pro
⛔ Files ignored due to path filters (1)
pnpm-lock.yamlis excluded by!**/pnpm-lock.yaml
📒 Files selected for processing (39)
e2e/vue-router/basic-vue-query-file-based/.gitignore(1 hunks)e2e/vue-router/basic-vue-query-file-based/index.html(1 hunks)e2e/vue-router/basic-vue-query-file-based/package.json(1 hunks)e2e/vue-router/basic-vue-query-file-based/playwright.config.ts(1 hunks)e2e/vue-router/basic-vue-query-file-based/postcss.config.mjs(1 hunks)e2e/vue-router/basic-vue-query-file-based/src/main.tsx(1 hunks)e2e/vue-router/basic-vue-query-file-based/src/postQueryOptions.tsx(1 hunks)e2e/vue-router/basic-vue-query-file-based/src/posts.tsx(1 hunks)e2e/vue-router/basic-vue-query-file-based/src/postsQueryOptions.tsx(1 hunks)e2e/vue-router/basic-vue-query-file-based/src/routeTree.gen.ts(1 hunks)e2e/vue-router/basic-vue-query-file-based/src/routes/__root.tsx(1 hunks)e2e/vue-router/basic-vue-query-file-based/src/routes/_layout.tsx(1 hunks)e2e/vue-router/basic-vue-query-file-based/src/routes/_layout/_layout-2.tsx(1 hunks)e2e/vue-router/basic-vue-query-file-based/src/routes/_layout/_layout-2/layout-a.tsx(1 hunks)e2e/vue-router/basic-vue-query-file-based/src/routes/_layout/_layout-2/layout-b.tsx(1 hunks)e2e/vue-router/basic-vue-query-file-based/src/routes/index.tsx(1 hunks)e2e/vue-router/basic-vue-query-file-based/src/routes/posts.$postId.tsx(1 hunks)e2e/vue-router/basic-vue-query-file-based/src/routes/posts.index.tsx(1 hunks)e2e/vue-router/basic-vue-query-file-based/src/routes/posts.tsx(1 hunks)e2e/vue-router/basic-vue-query-file-based/src/styles.css(1 hunks)e2e/vue-router/basic-vue-query-file-based/tests/app.spec.ts(1 hunks)e2e/vue-router/basic-vue-query-file-based/tests/setup/global.setup.ts(1 hunks)e2e/vue-router/basic-vue-query-file-based/tests/setup/global.teardown.ts(1 hunks)e2e/vue-router/basic-vue-query-file-based/tsconfig.json(1 hunks)e2e/vue-router/basic-vue-query-file-based/vite.config.js(1 hunks)e2e/vue-router/basic-vue-query/.gitignore(1 hunks)e2e/vue-router/basic-vue-query/index.html(1 hunks)e2e/vue-router/basic-vue-query/package.json(1 hunks)e2e/vue-router/basic-vue-query/playwright.config.ts(1 hunks)e2e/vue-router/basic-vue-query/postcss.config.mjs(1 hunks)e2e/vue-router/basic-vue-query/src/main.tsx(1 hunks)e2e/vue-router/basic-vue-query/src/posts.lazy.tsx(1 hunks)e2e/vue-router/basic-vue-query/src/posts.ts(1 hunks)e2e/vue-router/basic-vue-query/src/styles.css(1 hunks)e2e/vue-router/basic-vue-query/tests/app.spec.ts(1 hunks)e2e/vue-router/basic-vue-query/tests/setup/global.setup.ts(1 hunks)e2e/vue-router/basic-vue-query/tests/setup/global.teardown.ts(1 hunks)e2e/vue-router/basic-vue-query/tsconfig.json(1 hunks)e2e/vue-router/basic-vue-query/vite.config.js(1 hunks)
🧰 Additional context used
📓 Path-based instructions (3)
**/*.{ts,tsx}
📄 CodeRabbit inference engine (AGENTS.md)
Use TypeScript strict mode with extensive type safety for all code
Files:
e2e/vue-router/basic-vue-query-file-based/src/postsQueryOptions.tsxe2e/vue-router/basic-vue-query/src/posts.tse2e/vue-router/basic-vue-query-file-based/src/routes/posts.index.tsxe2e/vue-router/basic-vue-query-file-based/src/posts.tsxe2e/vue-router/basic-vue-query-file-based/tests/app.spec.tse2e/vue-router/basic-vue-query-file-based/tests/setup/global.teardown.tse2e/vue-router/basic-vue-query-file-based/src/routes/posts.$postId.tsxe2e/vue-router/basic-vue-query-file-based/src/routes/_layout.tsxe2e/vue-router/basic-vue-query-file-based/src/routes/_layout/_layout-2/layout-b.tsxe2e/vue-router/basic-vue-query-file-based/src/postQueryOptions.tsxe2e/vue-router/basic-vue-query/tests/setup/global.teardown.tse2e/vue-router/basic-vue-query-file-based/src/routes/_layout/_layout-2.tsxe2e/vue-router/basic-vue-query-file-based/src/routes/_layout/_layout-2/layout-a.tsxe2e/vue-router/basic-vue-query/src/posts.lazy.tsxe2e/vue-router/basic-vue-query/tests/setup/global.setup.tse2e/vue-router/basic-vue-query-file-based/src/main.tsxe2e/vue-router/basic-vue-query-file-based/playwright.config.tse2e/vue-router/basic-vue-query-file-based/src/routeTree.gen.tse2e/vue-router/basic-vue-query-file-based/src/routes/posts.tsxe2e/vue-router/basic-vue-query/src/main.tsxe2e/vue-router/basic-vue-query/tests/app.spec.tse2e/vue-router/basic-vue-query-file-based/tests/setup/global.setup.tse2e/vue-router/basic-vue-query-file-based/src/routes/__root.tsxe2e/vue-router/basic-vue-query-file-based/src/routes/index.tsxe2e/vue-router/basic-vue-query/playwright.config.ts
**/*.{js,ts,tsx}
📄 CodeRabbit inference engine (AGENTS.md)
Implement ESLint rules for router best practices using the ESLint plugin router
Files:
e2e/vue-router/basic-vue-query-file-based/src/postsQueryOptions.tsxe2e/vue-router/basic-vue-query/src/posts.tse2e/vue-router/basic-vue-query/vite.config.jse2e/vue-router/basic-vue-query-file-based/src/routes/posts.index.tsxe2e/vue-router/basic-vue-query-file-based/src/posts.tsxe2e/vue-router/basic-vue-query-file-based/tests/app.spec.tse2e/vue-router/basic-vue-query-file-based/tests/setup/global.teardown.tse2e/vue-router/basic-vue-query-file-based/src/routes/posts.$postId.tsxe2e/vue-router/basic-vue-query-file-based/src/routes/_layout.tsxe2e/vue-router/basic-vue-query-file-based/src/routes/_layout/_layout-2/layout-b.tsxe2e/vue-router/basic-vue-query-file-based/src/postQueryOptions.tsxe2e/vue-router/basic-vue-query/tests/setup/global.teardown.tse2e/vue-router/basic-vue-query-file-based/src/routes/_layout/_layout-2.tsxe2e/vue-router/basic-vue-query-file-based/src/routes/_layout/_layout-2/layout-a.tsxe2e/vue-router/basic-vue-query/src/posts.lazy.tsxe2e/vue-router/basic-vue-query/tests/setup/global.setup.tse2e/vue-router/basic-vue-query-file-based/src/main.tsxe2e/vue-router/basic-vue-query-file-based/vite.config.jse2e/vue-router/basic-vue-query-file-based/playwright.config.tse2e/vue-router/basic-vue-query-file-based/src/routeTree.gen.tse2e/vue-router/basic-vue-query-file-based/src/routes/posts.tsxe2e/vue-router/basic-vue-query/src/main.tsxe2e/vue-router/basic-vue-query/tests/app.spec.tse2e/vue-router/basic-vue-query-file-based/tests/setup/global.setup.tse2e/vue-router/basic-vue-query-file-based/src/routes/__root.tsxe2e/vue-router/basic-vue-query-file-based/src/routes/index.tsxe2e/vue-router/basic-vue-query/playwright.config.ts
**/package.json
📄 CodeRabbit inference engine (AGENTS.md)
Use workspace protocol
workspace:*for internal dependencies in package.json files
Files:
e2e/vue-router/basic-vue-query/package.jsone2e/vue-router/basic-vue-query-file-based/package.json
🧠 Learnings (10)
📓 Common learnings
Learnt from: nlynzaad
Repo: TanStack/router PR: 5402
File: packages/router-generator/tests/generator/no-formatted-route-tree/routeTree.nonnested.snapshot.ts:19-21
Timestamp: 2025-10-08T08:11:47.088Z
Learning: Test snapshot files in the router-generator tests directory (e.g., files matching the pattern `packages/router-generator/tests/generator/**/routeTree*.snapshot.ts` or `routeTree*.snapshot.js`) should not be modified or have issues flagged, as they are fixtures used to verify the generator's output and are intentionally preserved as-is.
Learnt from: FatahChan
Repo: TanStack/router PR: 5475
File: e2e/react-start/basic-prerendering/src/routes/redirect/$target/via-beforeLoad.tsx:8-0
Timestamp: 2025-10-14T18:59:33.990Z
Learning: In TanStack Router e2e test files, when a route parameter is validated at the route level (e.g., using zod in validateSearch or param validation), switch statements on that parameter do not require a default case, as the validation ensures only expected values will reach the switch.
📚 Learning: 2025-10-01T18:31:35.420Z
Learnt from: schiller-manuel
Repo: TanStack/router PR: 5330
File: e2e/react-start/custom-basepath/src/routeTree.gen.ts:58-61
Timestamp: 2025-10-01T18:31:35.420Z
Learning: Do not review files named `routeTree.gen.ts` in TanStack Router repositories, as these are autogenerated files that should not be manually modified.
Applied to files:
e2e/vue-router/basic-vue-query/.gitignoree2e/vue-router/basic-vue-query-file-based/.gitignoree2e/vue-router/basic-vue-query-file-based/src/routes/_layout.tsxe2e/vue-router/basic-vue-query-file-based/src/routes/_layout/_layout-2/layout-b.tsxe2e/vue-router/basic-vue-query-file-based/src/routes/_layout/_layout-2.tsxe2e/vue-router/basic-vue-query-file-based/tsconfig.jsone2e/vue-router/basic-vue-query-file-based/src/routeTree.gen.tse2e/vue-router/basic-vue-query-file-based/src/routes/__root.tsxe2e/vue-router/basic-vue-query-file-based/src/routes/index.tsx
📚 Learning: 2025-10-08T08:11:47.088Z
Learnt from: nlynzaad
Repo: TanStack/router PR: 5402
File: packages/router-generator/tests/generator/no-formatted-route-tree/routeTree.nonnested.snapshot.ts:19-21
Timestamp: 2025-10-08T08:11:47.088Z
Learning: Test snapshot files in the router-generator tests directory (e.g., files matching the pattern `packages/router-generator/tests/generator/**/routeTree*.snapshot.ts` or `routeTree*.snapshot.js`) should not be modified or have issues flagged, as they are fixtures used to verify the generator's output and are intentionally preserved as-is.
Applied to files:
e2e/vue-router/basic-vue-query/.gitignoree2e/vue-router/basic-vue-query-file-based/src/routes/posts.index.tsxe2e/vue-router/basic-vue-query-file-based/.gitignoree2e/vue-router/basic-vue-query-file-based/tests/app.spec.tse2e/vue-router/basic-vue-query-file-based/tests/setup/global.teardown.tse2e/vue-router/basic-vue-query/tsconfig.jsone2e/vue-router/basic-vue-query-file-based/src/routes/_layout.tsxe2e/vue-router/basic-vue-query-file-based/src/routes/_layout/_layout-2/layout-b.tsxe2e/vue-router/basic-vue-query-file-based/src/routes/_layout/_layout-2.tsxe2e/vue-router/basic-vue-query-file-based/src/routes/_layout/_layout-2/layout-a.tsxe2e/vue-router/basic-vue-query/tests/setup/global.setup.tse2e/vue-router/basic-vue-query-file-based/tsconfig.jsone2e/vue-router/basic-vue-query-file-based/src/routeTree.gen.tse2e/vue-router/basic-vue-query-file-based/src/routes/posts.tsxe2e/vue-router/basic-vue-query/src/main.tsxe2e/vue-router/basic-vue-query/tests/app.spec.tse2e/vue-router/basic-vue-query/package.jsone2e/vue-router/basic-vue-query-file-based/tests/setup/global.setup.tse2e/vue-router/basic-vue-query-file-based/src/routes/__root.tsxe2e/vue-router/basic-vue-query-file-based/src/routes/index.tsxe2e/vue-router/basic-vue-query-file-based/package.json
📚 Learning: 2025-12-06T15:03:07.223Z
Learnt from: CR
Repo: TanStack/router PR: 0
File: AGENTS.md:0-0
Timestamp: 2025-12-06T15:03:07.223Z
Learning: Use file-based routing in `src/routes/` directories or code-based routing with route definitions
Applied to files:
e2e/vue-router/basic-vue-query-file-based/src/routes/posts.index.tsxe2e/vue-router/basic-vue-query-file-based/src/routes/_layout.tsxe2e/vue-router/basic-vue-query-file-based/src/routes/_layout/_layout-2/layout-b.tsxe2e/vue-router/basic-vue-query-file-based/src/routes/_layout/_layout-2.tsxe2e/vue-router/basic-vue-query-file-based/src/routes/_layout/_layout-2/layout-a.tsxe2e/vue-router/basic-vue-query-file-based/src/routeTree.gen.tse2e/vue-router/basic-vue-query-file-based/src/routes/posts.tsxe2e/vue-router/basic-vue-query-file-based/src/routes/index.tsx
📚 Learning: 2025-12-06T15:03:07.223Z
Learnt from: CR
Repo: TanStack/router PR: 0
File: AGENTS.md:0-0
Timestamp: 2025-12-06T15:03:07.223Z
Learning: Applies to **/*.{js,ts,tsx} : Implement ESLint rules for router best practices using the ESLint plugin router
Applied to files:
e2e/vue-router/basic-vue-query-file-based/src/routes/posts.index.tsxe2e/vue-router/basic-vue-query-file-based/tests/app.spec.tse2e/vue-router/basic-vue-query-file-based/src/routes/posts.$postId.tsxe2e/vue-router/basic-vue-query/tsconfig.jsone2e/vue-router/basic-vue-query-file-based/src/routes/_layout.tsxe2e/vue-router/basic-vue-query-file-based/src/routes/_layout/_layout-2/layout-b.tsxe2e/vue-router/basic-vue-query-file-based/src/routes/_layout/_layout-2.tsxe2e/vue-router/basic-vue-query-file-based/tsconfig.jsone2e/vue-router/basic-vue-query-file-based/src/routeTree.gen.tse2e/vue-router/basic-vue-query-file-based/src/routes/posts.tsxe2e/vue-router/basic-vue-query/src/main.tsxe2e/vue-router/basic-vue-query/tests/app.spec.tse2e/vue-router/basic-vue-query/package.jsone2e/vue-router/basic-vue-query-file-based/src/routes/__root.tsxe2e/vue-router/basic-vue-query-file-based/src/routes/index.tsxe2e/vue-router/basic-vue-query-file-based/package.json
📚 Learning: 2025-10-09T12:59:02.129Z
Learnt from: hokkyss
Repo: TanStack/router PR: 5418
File: e2e/react-start/custom-identifier-prefix/src/styles/app.css:19-21
Timestamp: 2025-10-09T12:59:02.129Z
Learning: In e2e test directories (paths containing `e2e/`), accessibility concerns like outline suppression patterns are less critical since the code is for testing purposes, not production use.
Applied to files:
e2e/vue-router/basic-vue-query-file-based/.gitignore
📚 Learning: 2025-12-06T15:03:07.223Z
Learnt from: CR
Repo: TanStack/router PR: 0
File: AGENTS.md:0-0
Timestamp: 2025-12-06T15:03:07.223Z
Learning: Applies to **/*.{ts,tsx} : Use TypeScript strict mode with extensive type safety for all code
Applied to files:
e2e/vue-router/basic-vue-query/tsconfig.jsone2e/vue-router/basic-vue-query-file-based/tsconfig.json
📚 Learning: 2025-11-02T16:16:24.898Z
Learnt from: nlynzaad
Repo: TanStack/router PR: 5732
File: packages/start-client-core/src/client/hydrateStart.ts:6-9
Timestamp: 2025-11-02T16:16:24.898Z
Learning: In packages/start-client-core/src/client/hydrateStart.ts, the `import/no-duplicates` ESLint disable is necessary for imports from `#tanstack-router-entry` and `#tanstack-start-entry` because both aliases resolve to the same placeholder file (`fake-start-entry.js`) in package.json during static analysis, even though they resolve to different files at runtime.
Applied to files:
e2e/vue-router/basic-vue-query/tsconfig.jsone2e/vue-router/basic-vue-query/tests/setup/global.setup.tse2e/vue-router/basic-vue-query/package.jsone2e/vue-router/basic-vue-query-file-based/tests/setup/global.setup.ts
📚 Learning: 2025-10-09T12:59:14.842Z
Learnt from: hokkyss
Repo: TanStack/router PR: 5418
File: e2e/react-start/custom-identifier-prefix/public/site.webmanifest:2-3
Timestamp: 2025-10-09T12:59:14.842Z
Learning: In e2e test fixtures (files under e2e directories), empty or placeholder values in configuration files like site.webmanifest are acceptable and should not be flagged unless the test specifically validates those fields.
Applied to files:
e2e/vue-router/basic-vue-query-file-based/playwright.config.tse2e/vue-router/basic-vue-query-file-based/tests/setup/global.setup.tse2e/vue-router/basic-vue-query/playwright.config.ts
📚 Learning: 2025-10-14T18:59:33.990Z
Learnt from: FatahChan
Repo: TanStack/router PR: 5475
File: e2e/react-start/basic-prerendering/src/routes/redirect/$target/via-beforeLoad.tsx:8-0
Timestamp: 2025-10-14T18:59:33.990Z
Learning: In TanStack Router e2e test files, when a route parameter is validated at the route level (e.g., using zod in validateSearch or param validation), switch statements on that parameter do not require a default case, as the validation ensures only expected values will reach the switch.
Applied to files:
e2e/vue-router/basic-vue-query/src/main.tsx
🧬 Code graph analysis (17)
e2e/vue-router/basic-vue-query-file-based/src/postsQueryOptions.tsx (2)
e2e/vue-router/basic-vue-query/src/posts.ts (1)
postsQueryOptions(45-48)e2e/vue-router/basic-vue-query-file-based/src/posts.tsx (1)
fetchPosts(32-37)
e2e/vue-router/basic-vue-query/src/posts.ts (2)
e2e/vue-router/basic-vue-query-file-based/src/posts.tsx (2)
PostType(3-7)fetchPost(17-30)e2e/vue-router/basic-vue-query-file-based/src/postQueryOptions.tsx (1)
postQueryOptions(4-8)
e2e/vue-router/basic-vue-query-file-based/src/routes/posts.index.tsx (8)
e2e/vue-router/basic-vue-query-file-based/src/routes/__root.tsx (1)
Route(6-18)e2e/vue-router/basic-vue-query-file-based/src/routes/_layout.tsx (1)
Route(3-5)e2e/vue-router/basic-vue-query-file-based/src/routes/_layout/_layout-2.tsx (1)
Route(3-5)e2e/vue-router/basic-vue-query-file-based/src/routes/_layout/_layout-2/layout-a.tsx (1)
Route(2-4)e2e/vue-router/basic-vue-query-file-based/src/routes/_layout/_layout-2/layout-b.tsx (1)
Route(2-4)e2e/vue-router/basic-vue-query-file-based/src/routes/index.tsx (1)
Route(3-5)e2e/vue-router/basic-vue-query-file-based/src/routes/posts.$postId.tsx (1)
Route(8-14)e2e/vue-router/basic-vue-query-file-based/src/routes/posts.tsx (1)
Route(5-9)
e2e/vue-router/basic-vue-query-file-based/tests/app.spec.ts (1)
scripts/set-ts-version.js (1)
packageJson(33-33)
e2e/vue-router/basic-vue-query-file-based/src/routes/_layout/_layout-2/layout-b.tsx (7)
e2e/vue-router/basic-vue-query-file-based/src/routes/__root.tsx (1)
Route(6-18)e2e/vue-router/basic-vue-query-file-based/src/routes/_layout.tsx (1)
Route(3-5)e2e/vue-router/basic-vue-query-file-based/src/routes/_layout/_layout-2/layout-a.tsx (1)
Route(2-4)e2e/vue-router/basic-vue-query-file-based/src/routes/index.tsx (1)
Route(3-5)e2e/vue-router/basic-vue-query-file-based/src/routes/posts.$postId.tsx (1)
Route(8-14)e2e/vue-router/basic-vue-query-file-based/src/routes/posts.index.tsx (1)
Route(3-5)e2e/vue-router/basic-vue-query-file-based/src/routes/posts.tsx (1)
Route(5-9)
e2e/vue-router/basic-vue-query-file-based/src/postQueryOptions.tsx (2)
e2e/vue-router/basic-vue-query/src/posts.ts (1)
postQueryOptions(39-43)e2e/vue-router/basic-vue-query-file-based/src/posts.tsx (1)
fetchPost(17-30)
e2e/vue-router/basic-vue-query-file-based/src/routes/_layout/_layout-2.tsx (2)
e2e/vue-router/basic-vue-query-file-based/src/routes/_layout.tsx (1)
Route(3-5)e2e/vue-router/basic-vue-query-file-based/src/routes/_layout/_layout-2/layout-a.tsx (1)
Route(2-4)
e2e/vue-router/basic-vue-query-file-based/src/routes/_layout/_layout-2/layout-a.tsx (9)
e2e/vue-router/basic-vue-query-file-based/src/routes/__root.tsx (1)
Route(6-18)e2e/vue-router/basic-vue-query-file-based/src/routes/_layout.tsx (1)
Route(3-5)e2e/vue-router/basic-vue-query-file-based/src/routes/_layout/_layout-2.tsx (1)
Route(3-5)e2e/vue-router/basic-vue-query-file-based/src/routes/_layout/_layout-2/layout-b.tsx (1)
Route(2-4)e2e/vue-router/basic-vue-query-file-based/src/routes/index.tsx (1)
Route(3-5)e2e/vue-router/basic-vue-query-file-based/src/routes/posts.$postId.tsx (1)
Route(8-14)e2e/vue-router/basic-vue-query-file-based/src/routes/posts.index.tsx (1)
Route(3-5)e2e/vue-router/basic-vue-query-file-based/src/routes/posts.tsx (1)
Route(5-9)e2e/vue-router/basic-vue-query/src/posts.lazy.tsx (1)
Route(5-7)
e2e/vue-router/basic-vue-query/src/posts.lazy.tsx (4)
e2e/vue-router/basic-vue-query-file-based/src/routes/posts.$postId.tsx (1)
Route(8-14)e2e/vue-router/basic-vue-query-file-based/src/routes/posts.tsx (1)
Route(5-9)e2e/vue-router/basic-vue-query-file-based/src/postsQueryOptions.tsx (1)
postsQueryOptions(4-7)e2e/vue-router/basic-vue-query/src/posts.ts (1)
postsQueryOptions(45-48)
e2e/vue-router/basic-vue-query/tests/setup/global.setup.ts (4)
e2e/vue-router/basic-vue-query-file-based/src/main.tsx (1)
setup(33-35)e2e/vue-router/basic-vue-query-file-based/tests/setup/global.setup.ts (1)
setup(4-6)e2e/vue-router/basic-vue-query/src/main.tsx (1)
setup(259-261)scripts/set-ts-version.js (1)
packageJson(33-33)
e2e/vue-router/basic-vue-query-file-based/playwright.config.ts (1)
scripts/set-ts-version.js (1)
packageJson(33-33)
e2e/vue-router/basic-vue-query-file-based/src/routeTree.gen.ts (2)
packages/router-generator/tests/generator/types-disabled/routeTree.nonnested.snapshot.js (3)
PostsRoute(17-21)PostsRouteWithChildren(42-42)PostsRouteChildren(38-40)e2e/solid-router/js-only-file-based/src/routeTree.gen.js (1)
PostsIndexRoute(35-39)
e2e/vue-router/basic-vue-query-file-based/src/routes/posts.tsx (1)
e2e/vue-router/basic-vue-query-file-based/src/postsQueryOptions.tsx (1)
postsQueryOptions(4-7)
e2e/vue-router/basic-vue-query/src/main.tsx (1)
e2e/vue-router/basic-vue-query/src/posts.ts (3)
postsQueryOptions(45-48)postQueryOptions(39-43)NotFoundError(4-4)
e2e/vue-router/basic-vue-query-file-based/tests/setup/global.setup.ts (2)
e2e/vue-router/basic-vue-query/src/main.tsx (1)
setup(259-261)scripts/set-ts-version.js (1)
packageJson(33-33)
e2e/vue-router/basic-vue-query-file-based/src/routes/__root.tsx (3)
e2e/vue-router/basic-vue-query-file-based/src/routes/index.tsx (1)
Route(3-5)e2e/vue-router/basic-vue-query-file-based/src/routes/posts.$postId.tsx (1)
Route(8-14)e2e/vue-router/basic-vue-query-file-based/src/routes/posts.tsx (1)
Route(5-9)
e2e/vue-router/basic-vue-query/playwright.config.ts (1)
scripts/set-ts-version.js (1)
packageJson(33-33)
⏰ 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). (2)
- GitHub Check: Test
- GitHub Check: Preview
🔇 Additional comments (37)
e2e/vue-router/basic-vue-query/vite.config.js (1)
1-8: Minimal Vite config looks correct for Vue + JSX.Imports and
defineConfig({ plugins: [vue(), vueJsx()] })are appropriate for a Vue 3 e2e scaffold; nothing concerning here.e2e/vue-router/basic-vue-query-file-based/tsconfig.json (2)
1-17: Solid baseline tsconfig for a Vite/Vue e2e scaffold.
ESNext +moduleResolution: "Bundler",noEmit, andstrict: trueare all aligned with modern Vite/Vue setups. As per retrieved learnings, strict mode is enabled.
10-16: No action required. The tsconfig configuration is correct for this Playwright-based e2e project.The
typesarray in TypeScript controls automatic inclusion of ambient type declarations from@typespackages, not module imports. When test files import from@playwright/test, TypeScript uses the types exported by the package itself—not affected by thetypesarray. The successful build (withvue-tsc --noEmit) confirms Playwright types resolve correctly. TheskipLibCheck: truesetting is intentional and consistent with the root tsconfig; it's a project-wide decision that doesn't interfere with test file type checking.e2e/vue-router/basic-vue-query-file-based/.gitignore (1)
1-11: Reasonable ignore set for build + Playwright artifacts.e2e/vue-router/basic-vue-query/.gitignore (1)
1-11: Matches the file-based suite; ignore set looks complete.e2e/vue-router/basic-vue-query-file-based/src/routes/_layout/_layout-2/layout-a.tsx (1)
1-8: LGTM for a basic file-route + component.Please confirm the new e2e workspace enables the repo’s ESLint “router” plugin rules for
**/*.{js,ts,tsx}(per coding guidelines).e2e/vue-router/basic-vue-query/tsconfig.json (1)
1-17: Strict TS + Vue TSX settings look appropriate for Vite.e2e/vue-router/basic-vue-query-file-based/src/routes/index.tsx (1)
1-13: Root route is fine; JSXclassusage is consistent with Vue JSX.Please confirm the new e2e workspace runs ESLint with the repo’s router best-practices plugin for these TSX routes (per coding guidelines).
e2e/vue-router/basic-vue-query/postcss.config.mjs (1)
1-5: No changes needed. The PostCSS Tailwind plugin configuration is correct for Tailwind v4. The project has@tailwindcss/postcss@^4.1.15,tailwindcss@^4.1.17, andpostcss@^8.5.1properly configured as dependencies, meeting all Tailwind v4 requirements. The config format matches the documented pattern for Tailwind v4 PostCSS integration in Vite/Vue projects.e2e/vue-router/basic-vue-query-file-based/postcss.config.mjs (1)
1-5: Tailwind v4 PostCSS configuration is correct and properly wired.@tailwindcss/postcssv4.1.15 andtailwindcssv4.1.17 are both present in package.json and the plugin configuration matches the official Tailwind v4 PostCSS setup. Consistent with all e2e test suites.e2e/vue-router/basic-vue-query-file-based/src/routes/posts.index.tsx (1)
1-9: Looks good for a/posts/index route.e2e/vue-router/basic-vue-query-file-based/src/routes/_layout.tsx (1)
1-16: Layout +<Outlet />wiring looks correct.e2e/vue-router/basic-vue-query-file-based/index.html (1)
1-12: Standard Vite entry HTML; no issues.e2e/vue-router/basic-vue-query/index.html (1)
1-12: Standard Vite entry HTML; no issues.e2e/vue-router/basic-vue-query/tests/setup/global.teardown.ts (1)
1-6: Verify Node/TS support for JSON import attributes (with { type: 'json' }).If CI/runtime doesn’t support this form, consider switching to a small
fs.readFile+JSON.parse(or align on the supported import-attributes syntax for your Node baseline).e2e/vue-router/basic-vue-query/package.json (1)
1-33: The review comment is based on outdated or misapplied guidelines. The entire e2e/ directory consistently usesworkspace:^across all framework variants (react, vue, solid), and this file correctly follows that established pattern. All other e2e packages useworkspace:^for their internal dependencies, including@tanstack/router-e2e-utils,@tanstack/vue-router, and@tanstack/vue-router-devtools. Changing toworkspace:*would create inconsistency with the rest of the e2e suite.Likely an incorrect or invalid review comment.
e2e/vue-router/basic-vue-query/src/posts.ts (1)
12-16: Environment variables are guaranteed to be set together by Playwright config.The
VITE_NODE_ENVandVITE_EXTERNAL_PORTvariables are always initialized together in the Playwright configuration command (playwright.config.ts line 29). Both are populated from async port derivation functions (getDummyServerPort()returns a number) before any build occurs. The conditional checkif (import.meta.env.VITE_NODE_ENV === 'test')ensures these variables are only used in the test build context where they're guaranteed to exist. No additional validation is needed.e2e/vue-router/basic-vue-query-file-based/tests/setup/global.setup.ts (1)
1-6: GuardpackageJson.nameand verify JSON-import syntax compatibility (with { type: 'json' }).If
nameis missing you’ll passundefinedtoe2eStartDummyServer, and thewith { type: 'json' }syntax may not be supported by the repo’s Playwright/TS pipeline.import { e2eStartDummyServer } from '@tanstack/router-e2e-utils' import packageJson from '../../package.json' with { type: 'json' } export default async function setup() { - await e2eStartDummyServer(packageJson.name) + if (!packageJson.name) throw new Error('package.json#name is required for e2e dummy server') + await e2eStartDummyServer(packageJson.name) }e2e/vue-router/basic-vue-query-file-based/src/styles.css (1)
1-21: LGTM: Tailwind v4 base layer + light/dark defaults look consistent.e2e/vue-router/basic-vue-query/tests/setup/global.setup.ts (1)
1-6: GuardpackageJson.nameand verify JSON-import syntax compatibility (with { type: 'json' }).import { e2eStartDummyServer } from '@tanstack/router-e2e-utils' import packageJson from '../../package.json' with { type: 'json' } export default async function setup() { - await e2eStartDummyServer(packageJson.name) + if (!packageJson.name) throw new Error('package.json#name is required for e2e dummy server') + await e2eStartDummyServer(packageJson.name) }e2e/vue-router/basic-vue-query-file-based/tests/setup/global.teardown.ts (1)
1-6: GuardpackageJson.nameand verify JSON-import syntax compatibility (with { type: 'json' }).import { e2eStopDummyServer } from '@tanstack/router-e2e-utils' import packageJson from '../../package.json' with { type: 'json' } export default async function teardown() { - await e2eStopDummyServer(packageJson.name) + if (!packageJson.name) throw new Error('package.json#name is required for e2e dummy server') + await e2eStopDummyServer(packageJson.name) }e2e/vue-router/basic-vue-query-file-based/src/postQueryOptions.tsx (1)
1-8: LGTM: queryKey/queryFn wiring matches expected Vue Query usage.e2e/vue-router/basic-vue-query-file-based/src/routes/_layout/_layout-2.tsx (1)
1-34: Verifyto="/layout-a"/to="/layout-b"match the actual generated URLs for these file-based routes.If
_layoutsegments are pathless in this fixture, this is fine; if not, these links will 404 and/or not activate as expected.e2e/vue-router/basic-vue-query/src/styles.css (1)
1-21: LGTM: consistent Tailwind v4 base + light/dark theming defaults.e2e/vue-router/basic-vue-query-file-based/src/posts.tsx (1)
1-37: Fix type mismatch and guardVITE_EXTERNAL_PORT(current code may fail in test mode).The code has two issues:
id: stringshould beid: number— JSONPlaceholder API returns post IDs as numeric values, not strings.VITE_EXTERNAL_PORTis not guarded; if missing in test mode, the URL becomeshttp://localhost:undefined.Regarding the 404 detection: the codebase consistently uses
err.status === 404across 60+ similar files with redaxios, suggesting this is the correct error structure for this library, despite some API documentation suggestingerr.response.status. If 404 handling is not working, the issue may be elsewhere (e.g., redaxios version, fetch behavior, or mocking setup in tests).export type PostType = { - id: string + id: number title: string body: string } export class PostNotFoundError extends Error {} let queryURL = 'https://jsonplaceholder.typicode.com' if (import.meta.env.VITE_NODE_ENV === 'test') { - queryURL = `http://localhost:${import.meta.env.VITE_EXTERNAL_PORT}` + const port = import.meta.env.VITE_EXTERNAL_PORT + if (!port) throw new Error('VITE_EXTERNAL_PORT is required in test mode') + queryURL = `http://localhost:${port}` }e2e/vue-router/basic-vue-query-file-based/src/routes/posts.tsx (1)
5-9: Loader + component query wiring looks correct for Vue Query prefetch.
UsingensureQueryDatain the loader anduseQueryin the component is a good e2e pattern for “prefetch then subscribe”.e2e/vue-router/basic-vue-query-file-based/src/main.tsx (2)
23-27: Module augmentation for router type safety looks good.
This should give nice end-to-end type checking for route definitions and Links.
7-20: No changes needed. BothdefaultPreload: 'intent'anddefaultPreloadStaleTime: 0are valid, well-supported options in @tanstack/vue-router. They are properly defined in the router-core options and actively used in the preload logic with appropriate defaults and fallbacks.e2e/vue-router/basic-vue-query-file-based/src/routes/_layout/_layout-2/layout-b.tsx (1)
1-8: Simple file-route component; looks good.
Matches the file-based routing conventions used elsewhere in the suite.e2e/vue-router/basic-vue-query-file-based/src/routes/__root.tsx (1)
6-18: Root route context typing + notFoundComponent are a good e2e coverage hook.
This cleanly exercises “not found” behavior and the queryClient plumbing.e2e/vue-router/basic-vue-query-file-based/playwright.config.ts (2)
15-40: Overall config is clear and appropriately scoped to a single chromium project.
Workers=1 and reuseExistingServer behavior are reasonable defaults for e2e fixtures.
1-11: Top-level await in this Playwright config is fully supported.The configuration correctly uses ESM mode with
"type": "module"in package.json, appropriate TypeScript settings (module: "ESNext", target: "ESNext"), and Playwright 1.56.1, all of which fully support top-level await. No changes are needed.e2e/vue-router/basic-vue-query-file-based/vite.config.js (1)
1-13: No issues found. The plugin order is correct—tanstackRoutermust come before the Vue and JSX plugins to transform file-based routes for automatic code splitting, and the code already follows this pattern.autoCodeSplitting: trueis properly enabled and requires no additional setup beyond being set in the plugin options, which is already done.e2e/vue-router/basic-vue-query-file-based/tests/app.spec.ts (1)
1-4: The JSON import assertion syntaxwith { type: 'json' }is properly supported in your TypeScript and Playwright configuration. All e2e projects haveresolveJsonModule: truein their tsconfigs, TypeScript 5.8+ fully supports import attributes, and this pattern is consistently used across the e2e suite. No runtime compatibility issues exist with this approach.However, consider using the Playwright
requestfixture instead of Node'sfetch()for HTTP assertions—it provides built-in retry logic, request/response logging, and tracing that are valuable for E2E tests.e2e/vue-router/basic-vue-query/src/main.tsx (2)
19-31: Typed router context wiring looks solid.
createRootRouteWithContext<{ queryClient: QueryClient }>()matches the loader usage and keeps loaders type-safe.
94-100: No action needed. The lazy contract is correctly implemented. Theposts.lazy.tsxfile exportsconst Route = createLazyRoute('/posts')({...}), and the code correctly accesses it viad.Routein the.then()callback. The export name and access pattern match—there is no mismatch that would cause runtime failures during navigation.e2e/vue-router/basic-vue-query/playwright.config.ts (1)
6-10: The import attributes syntax and top-level await are already used and working in this file and throughout the entire e2e codebase. The setup supports these features: TypeScript 5.8.3 (supports import attributes since 5.3),resolveJsonModule: truein tsconfig, and ESNext target/module settings. The suggested change tocreateRequireis unnecessary—the current modern syntax is properly supported and functioning.Likely an incorrect or invalid review comment.
| { | ||
| "name": "tanstack-router-e2e-vue-basic-vue-query-file-based", | ||
| "private": true, | ||
| "type": "module", | ||
| "scripts": { | ||
| "dev": "vite --port 3000", | ||
| "dev:e2e": "vite", | ||
| "build": "vite build && vue-tsc --noEmit", | ||
| "preview": "vite preview", | ||
| "start": "vite", | ||
| "test:e2e": "rm -rf port*.txt; playwright test --project=chromium" | ||
| }, | ||
| "dependencies": { | ||
| "@tailwindcss/postcss": "^4.1.15", | ||
| "@tanstack/vue-query": "^5.90.0", | ||
| "@tanstack/vue-query-devtools": "^6.1.2", | ||
| "@tanstack/vue-router": "workspace:^", | ||
| "@tanstack/vue-router-devtools": "workspace:^", | ||
| "@tanstack/router-plugin": "workspace:^", | ||
| "postcss": "^8.5.1", | ||
| "redaxios": "^0.5.1", | ||
| "tailwindcss": "^4.1.17", | ||
| "vue": "^3.5.16", | ||
| "zod": "^3.24.2" | ||
| }, | ||
| "devDependencies": { | ||
| "@playwright/test": "^1.50.1", | ||
| "@tanstack/router-e2e-utils": "workspace:^", | ||
| "@vitejs/plugin-vue": "^5.2.3", | ||
| "@vitejs/plugin-vue-jsx": "^4.1.2", | ||
| "typescript": "~5.8.3", | ||
| "vite": "^7.1.7", | ||
| "vue-tsc": "^3.1.5" | ||
| } | ||
| } |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
🧩 Analysis chain
🏁 Script executed:
# Find all package.json files to see what pattern is used
git ls-files "**package.json" | head -20Repository: TanStack/router
Length of output: 1105
🏁 Script executed:
# Look for workspace protocol usage patterns in the repo
rg "workspace:\*|workspace:\^" --type json | head -30Repository: TanStack/router
Length of output: 2758
🏁 Script executed:
# Check for workspace protocol documentation in the repo
rg "workspace:\*|workspace:\^" --type md -B 2 -A 2 | head -50Repository: TanStack/router
Length of output: 580
🏁 Script executed:
# Check the git history to see when these guidelines were introduced
git log --all --oneline -- "**/AGENTS.md" "**/*guidelines*" | head -10Repository: TanStack/router
Length of output: 41
🏁 Script executed:
# Check root package.json to understand workspace configuration
cat -n package.json | head -50Repository: TanStack/router
Length of output: 2574
🏁 Script executed:
# Check git log for changes to AGENTS.md related to workspace protocol
git log --all --oneline --follow -- AGENTS.md | head -20Repository: TanStack/router
Length of output: 180
🏁 Script executed:
# Look at the actual diff/content changes in AGENTS.md
git log --all --pretty=format:"%h %s" -- AGENTS.md | head -5Repository: TanStack/router
Length of output: 180
🏁 Script executed:
# Check if there are any recent commits updating package.json files to workspace:*
git log --all --oneline --grep="workspace" | head -10Repository: TanStack/router
Length of output: 512
🏁 Script executed:
# Check a few other e2e package.json files to confirm the pattern is universal
for file in e2e/react-router/basic-file-based/package.json e2e/solid-start/website/package.json e2e/vue-router/basic/package.json; do
echo "=== $file ===";
rg "workspace:" "$file" | head -5;
doneRepository: TanStack/router
Length of output: 774
Use workspace:* for internal dependencies per repo guidelines.
"dependencies": {
- "@tanstack/vue-router": "workspace:^",
- "@tanstack/vue-router-devtools": "workspace:^",
- "@tanstack/router-plugin": "workspace:^",
+ "@tanstack/vue-router": "workspace:*",
+ "@tanstack/vue-router-devtools": "workspace:*",
+ "@tanstack/router-plugin": "workspace:*",
"devDependencies": {
- "@tanstack/router-e2e-utils": "workspace:^",
+ "@tanstack/router-e2e-utils": "workspace:*",Note: The entire e2e suite currently uses workspace:^, so consider applying this guideline repo-wide for consistency.
🤖 Prompt for AI Agents
In e2e/vue-router/basic-vue-query-file-based/package.json lines 1-35, internal
workspace dependencies use "workspace:^"; update every occurrence of
"workspace:^" to "workspace:*" (for @tanstack/vue-router,
@tanstack/vue-router-devtools, @tanstack/router-plugin,
@tanstack/router-e2e-utils) so internal packages follow repo guidelines; save
the file and run a quick install to verify lockfile updates.
| export const Route = createFileRoute('/posts/$postId')({ | ||
| loader: ({ context: { queryClient }, params: { postId } }) => { | ||
| return queryClient.ensureQueryData(postQueryOptions(postId)) | ||
| }, | ||
| errorComponent: PostErrorComponent, | ||
| component: PostComponent, | ||
| }) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
🧩 Analysis chain
🏁 Script executed:
cat -n e2e/vue-router/basic-vue-query-file-based/src/routes/posts.\$postId.tsxRepository: TanStack/router
Length of output: 1690
🏁 Script executed:
find . -name "*postQueryOptions*" -type fRepository: TanStack/router
Length of output: 595
🏁 Script executed:
cat -n e2e/vue-router/basic-vue-query-file-based/src/postQueryOptions.tsxRepository: TanStack/router
Length of output: 351
🏁 Script executed:
rg "useQuery" e2e/vue-router/basic-vue-query-file-based/src/routes/ -A 2 -B 2Repository: TanStack/router
Length of output: 2033
🏁 Script executed:
cat -n e2e/vue-router/basic-vue-query-file-based/src/posts.tsx | head -30Repository: TanStack/router
Length of output: 980
Loader + useQuery() causes unnecessary refetch with default staleTime.
The loader: ensureQueryData(...) populates the cache, but with postQueryOptions having no explicit staleTime, TanStack Query defaults to staleTime=0 (immediately stale). When the component mounts, useQuery(postQueryOptions(postId)) sees stale data and refetches, causing a duplicate network request. Since the component renders from Route.useLoaderData() and ignores the useQuery() result, either set a positive staleTime in postQueryOptions, use the result from useQuery(), or disable the mount refetch with enabled: false.
🤖 Prompt for AI Agents
In e2e/vue-router/basic-vue-query-file-based/src/routes/posts.$postId.tsx around
lines 8–14, the loader uses queryClient.ensureQueryData but postQueryOptions has
no explicit staleTime so TanStack Query treats the cached data as immediately
stale and the component’s useQuery triggers a duplicate refetch on mount; fix by
giving postQueryOptions a positive staleTime (e.g. minutes) so the
loader-populated data stays fresh, or alternatively change the component to
consume the loader data from Route.useLoaderData() instead of calling useQuery,
or disable the automatic refetch on mount by configuring useQuery with enabled:
false or refetchOnMount: false.
| test('GetPosts', async () => { | ||
| const port = await getDummyServerPort(packageJson.name) | ||
| const res = await fetch(`http://localhost:${port}/posts`) | ||
|
|
||
| expect(res.status).toBe(200) | ||
|
|
||
| const posts = await res.json() | ||
|
|
||
| expect(posts.length).toBeGreaterThan(0) | ||
|
|
||
| const postRes = await fetch(`http://localhost:${port}/posts/1`) | ||
| expect(postRes.status).toBe(200) | ||
| const post = await postRes.json() | ||
| expect(post).toEqual(posts[0]) | ||
| }) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
🧩 Analysis chain
🏁 Script executed:
#!/bin/bash
# Find and examine package.json to check Playwright and Node versions
fd -t f "package\.json" | head -5Repository: TanStack/router
Length of output: 174
🏁 Script executed:
#!/bin/bash
# Check the root package.json for Playwright version and Node requirements
cat package.json | head -50Repository: TanStack/router
Length of output: 2224
🏁 Script executed:
#!/bin/bash
# Look for Playwright config files
fd -t f "playwright\.config\.(ts|js)"Repository: TanStack/router
Length of output: 4026
🏁 Script executed:
#!/bin/bash
# Check the Playwright config for this specific test
cat "e2e/vue-router/basic-vue-query-file-based/playwright.config.ts"Repository: TanStack/router
Length of output: 1208
🏁 Script executed:
#!/bin/bash
# Read the full test file to understand context
cat "e2e/vue-router/basic-vue-query-file-based/tests/app.spec.ts"Repository: TanStack/router
Length of output: 1937
🏁 Script executed:
#!/bin/bash
# Search for fetch usage in test files across the e2e directory
rg "fetch\(" e2e/ -t ts -t tsx --max-count=20 | head -40Repository: TanStack/router
Length of output: 86
🏁 Script executed:
#!/bin/bash
# Search for request fixture usage in test files
rg "request\." e2e/ -t ts --max-count=30Repository: TanStack/router
Length of output: 8586
🏁 Script executed:
#!/bin/bash
# Search for async test signatures to see what fixtures are typically injected
rg "test\('.*',\s+async\s+\(\{" e2e/ -t ts | head -20Repository: TanStack/router
Length of output: 2544
🏁 Script executed:
#!/bin/bash
# Check if there are any other fetch() calls in test files
rg "await fetch\(" e2e/ -t ts -B 2 -A 2 | head -60Repository: TanStack/router
Length of output: 5015
🌐 Web query:
Playwright request fixture vs fetch API in tests best practices
💡 Result:
Short answer — prefer Playwright's request fixture / APIRequestContext for test setup, API-driven auth, and backend checks; use page.fetch/page.evaluate or browser-side fetch only when you must exercise the in-page JavaScript environment or need the request to originate from the page (same-origin, JS headers, ServiceWorker, or instrumentation). See official docs for request fixture and APIRequestContext behavior. [1][2][3]
Recommended best practices
- Use the built-in request fixture (APIRequestContext) for test setup/teardown, creating test data, calling internal APIs, and getting/setting authentication state. It is fast, isolated per-test, and can persist storageState to reuse cookies between API and browser contexts. [1][2][3]
- Prefer request.post/get/fetch from APIRequestContext over Node fetch or custom HTTP clients inside tests because Playwright manages timeouts, cookies, redirects, and integration with BrowserContext storageState. That reduces flakiness and simplifies storageState interchange. [2][3]
- Use page.request or browser_context.request (when you need the request to share the BrowserContext cookie jar) if you want requests that share cookies with the page; use a standalone request.newContext when you want an isolated API client with custom baseURL/credentials. [3]
- Use in-page fetch (page.evaluate(() => fetch(...))) when you must exercise client-side JavaScript behavior, ServiceWorkers, CORS behavior, or same-origin constraints that only appear inside the page. Avoid it for pure API-level setup because it’s slower, more brittle, and entangles tests with page state.
- Keep API tests and E2E UI flows distinct: use APIRequestContext for API tests and environment setup; drive user flows via page interactions. When combining (e.g., log in via API then test UI), log in with request, save storageState, then create BrowserContext with that state. [2][3]
- Centralize shared API helpers/fixtures (baseURL, auth helpers, test data creation) in test fixtures so tests remain readable and teardown is reliable. Use proper scopes (per-test vs per-suite) depending on isolation needs. [1]
When to choose which (quick checklist)
- Need speed, stability, auth setup, or server-side state checks → request fixture / APIRequestContext. [2]
- Need to test client-side JS, ServiceWorker, CORS, or observe how the page issues requests → in-page fetch / page.evaluate. [3]
- Need requests that share the same cookie storage as a BrowserContext page → page.request or browserContext.request. [3]
References
- Playwright fixtures and built-in request fixture docs. [1]
- Playwright API testing guide (request.newContext, storageState, examples). [2]
- APIRequestContext cookie-sharing and fetch behavior (cookie/storageState details). [3]
Use Playwright's request fixture to make API calls in tests instead of global fetch().
This test should inject the request fixture as a parameter and use request.get() for API calls. The Playwright request fixture provides proper timeout handling, cookie management, and integration with BrowserContext state—all of which are lost when using global fetch(). Change the test signature from async () => { to async ({ request }) => { and replace await fetch(...) with await request.get(...).
🤖 Prompt for AI Agents
In e2e/vue-router/basic-vue-query-file-based/tests/app.spec.ts around lines 9 to
23, the test uses global fetch() which bypasses Playwright's request fixture;
change the test signature to accept the Playwright fixture (async ({ request })
=> { ...) and replace fetch(...) calls with await request.get(...) (use
response.status() and await response.json() accordingly) so requests use
Playwright's timeout, cookie and context handling.
| const postRoute = createRoute({ | ||
| getParentRoute: () => postsRoute, | ||
| path: '$postId', | ||
| errorComponent: PostErrorComponent, | ||
| loader: ({ context: { queryClient }, params: { postId } }) => | ||
| queryClient.ensureQueryData(postQueryOptions(postId)), | ||
| component: PostRouteComponent, | ||
| }) | ||
|
|
||
| function PostErrorComponent({ error }: ErrorComponentProps) { | ||
| const router = useRouter() | ||
| if (error instanceof NotFoundError) { | ||
| return <div>{error.message}</div> | ||
| } | ||
|
|
||
| return ( | ||
| <div> | ||
| <button | ||
| onClick={() => { | ||
| router.invalidate() | ||
| }} | ||
| > | ||
| retry | ||
| </button> | ||
| <ErrorComponent error={error} /> | ||
| </div> | ||
| ) | ||
| } | ||
|
|
||
| function PostRouteComponent() { | ||
| const postId = postRoute.useParams().value.postId | ||
| useQuery(postQueryOptions(postId)) | ||
| const post = postRoute.useLoaderData() | ||
|
|
||
| return ( | ||
| <div class="space-y-2"> | ||
| <h4 class="text-xl font-bold underline">{post.value.title}</h4> | ||
| <div class="text-sm">{post.value.body}</div> | ||
| </div> | ||
| ) | ||
| } |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Same concern as file-based: loader + useQuery() may refetch, and you’re not rendering from query data.
If you want to keep loader-driven rendering, consider disabling mount refetch for the query observer; if you want query-driven rendering, render from useQuery’s data (optionally falling back to loader data).
🤖 Prompt for AI Agents
In e2e/vue-router/basic-vue-query/src/main.tsx around lines 111 to 151, the
component uses a loader plus a separate useQuery which can trigger an extra
refetch and then the UI renders from loader data instead of the query result;
either disable mount refetch on the query observer or render from the query data
(with loader as fallback). Fix by updating the useQuery call to include
refetchOnMount: false (and any other observer options to prevent duplicate
fetches) if you want loader-driven rendering, or change the component to read
data from the useQuery result (e.g., const { data } = useQuery(...); render from
data with postRoute.useLoaderData() as a fallback) if you want query-driven
rendering.
| const postsQuery = useQuery(postsQueryOptions) | ||
| const posts = postsQuery.data |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Add loading and error state handling.
The component doesn't handle loading or error states from the query. This means:
- During initial load, users see an empty list without feedback
- If the query fails, there's no error message displayed
- The app may crash if
posts.valueis accessed in an error state
Consider adding state handling:
function PostsComponent() {
const postsQuery = useQuery(postsQueryOptions)
- const posts = postsQuery.data
+ const posts = postsQuery.data
+
+ if (postsQuery.isLoading) {
+ return <div class="p-2">Loading posts...</div>
+ }
+
+ if (postsQuery.isError) {
+ return <div class="p-2 text-red-600">Error loading posts: {postsQuery.error.message}</div>
+ }
return (
<div class="p-2 flex gap-2">📝 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.
| const postsQuery = useQuery(postsQueryOptions) | |
| const posts = postsQuery.data | |
| const postsQuery = useQuery(postsQueryOptions) | |
| const posts = postsQuery.data | |
| if (postsQuery.isLoading) { | |
| return <div class="p-2">Loading posts...</div> | |
| } | |
| if (postsQuery.isError) { | |
| return <div class="p-2 text-red-600">Error loading posts: {postsQuery.error.message}</div> | |
| } |
🤖 Prompt for AI Agents
In e2e/vue-router/basic-vue-query/src/posts.lazy.tsx around lines 10 to 11, the
component reads postsQuery.data directly and doesn't handle loading or error
states; update the component to check postsQuery.isLoading and
postsQuery.isError (or postsQuery.error) before accessing postsQuery.data,
render a loading indicator (e.g., "Loading..." or a spinner) while isLoading is
true, render a user-visible error message when isError is true and avoid
accessing postsQuery.data in that case, and only map/render posts when
postsQuery.data (or posts.value) is defined; ensure any template/JSX branches
return early or conditional-render to prevent crashes.
| let queryURL = 'https://jsonplaceholder.typicode.com' | ||
|
|
||
| if (import.meta.env.VITE_NODE_ENV === 'test') { | ||
| queryURL = `http://localhost:${import.meta.env.VITE_EXTERNAL_PORT}` | ||
| } | ||
|
|
||
| const fetchPosts = async () => { | ||
| console.info('Fetching posts...') | ||
| return axios | ||
| .get<Array<PostType>>(`${queryURL}/posts`) | ||
| .then((r) => r.data.slice(0, 10)) | ||
| } | ||
|
|
||
| const fetchPost = async (postId: string) => { | ||
| console.info(`Fetching post with id ${postId}...`) | ||
| const post = await axios | ||
| .get<PostType>(`${queryURL}/posts/${postId}`) | ||
| .then((r) => r.data) | ||
|
|
||
| // eslint-disable-next-line @typescript-eslint/no-unnecessary-condition | ||
| if (!post) { | ||
| throw new NotFoundError(`Post with id "${postId}" not found!`) | ||
| } | ||
|
|
||
| return post | ||
| } |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Fix not-found handling: fetchPost likely never throws NotFoundError on 404.
If redaxios rejects on non-2xx (common), aFC, the code won’t reach if (!post). Map 404 explicitly:
const fetchPost = async (postId: string) => {
console.info(`Fetching post with id ${postId}...`)
- const post = await axios
- .get<PostType>(`${queryURL}/posts/${postId}`)
- .then((r) => r.data)
-
- // eslint-disable-next-line @typescript-eslint/no-unnecessary-condition
- if (!post) {
- throw new NotFoundError(`Post with id "${postId}" not found!`)
- }
-
- return post
+ return axios
+ .get<PostType>(`${queryURL}/posts/${postId}`)
+ .then((r) => r.data)
+ .catch((err) => {
+ if (err?.status === 404) {
+ throw new NotFoundError(`Post with id "${postId}" not found!`)
+ }
+ throw err
+ })
}🤖 Prompt for AI Agents
In e2e/vue-router/basic-vue-query/src/posts.ts around lines 12 to 37, fetchPost
currently assumes a falsy response to detect 404 but axios/redaxios will reject
on non-2xx so that check never runs; update fetchPost to catch the request
error, inspect error.response?.status and if it is 404 throw the NotFoundError
with the postId, otherwise rethrow the original error (or wrap with a
descriptive error) so other failures surface normally; ensure the error type
check is safe (use optional chaining) and preserve return of the post when
successful.
add vue-router suites
Summary by CodeRabbit
✏️ Tip: You can customize this high-level summary in your review settings.