Skip to content

Conversation

@danielroe
Copy link
Member

@danielroe danielroe commented Feb 3, 2026

this is a first step to using api mocking on the server to make CI tests (lighthouse + playwright) faster and less flaky

the majority of the time is still building the app, but this should help

before
SCR-20260203-lbhn

after
SCR-20260203-lbkr

@vercel
Copy link

vercel bot commented Feb 3, 2026

The latest updates on your projects. Learn more about Vercel for GitHub.

Project Deployment Actions Updated (UTC)
npmx.dev Ready Ready Preview, Comment Feb 3, 2026 6:49pm
2 Skipped Deployments
Project Deployment Actions Updated (UTC)
docs.npmx.dev Ignored Ignored Preview Feb 3, 2026 6:49pm
npmx-lunaria Ignored Ignored Feb 3, 2026 6:49pm

Request Review

@coderabbitai
Copy link
Contributor

coderabbitai bot commented Feb 3, 2026

📝 Walkthrough

Walkthrough

This pull request adds an end-to-end test fixtures system: a Nuxt module (modules/fixtures.ts) and Nitro plugin (modules/runtime/server/cache.ts) that intercept server fetches and serve fixtures from disk; a fixture generator script (scripts/generate-fixtures.ts); a Playwright test helper (test/e2e/test-utils.ts) to mock external APIs; updated E2E tests to use the local test utilities; CI workflow tweak to set NODE_ENV=test during build; numerous test fixture files under test/fixtures; and a package script generate:fixtures.

🚥 Pre-merge checks | ✅ 1
✅ Passed checks (1 passed)
Check name Status Explanation
Description check ✅ Passed The PR description clearly explains the purpose: implementing server-side API mocking using fixtures to improve CI test performance and reduce flakiness, with before/after timing comparisons.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing touches
  • 📝 Generate docstrings
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Post copyable unit tests in a comment
  • Commit unit tests in branch test/fixtures

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

// Main
// ============================================================================

async function main(): Promise<void> {
Copy link
Contributor

Choose a reason for hiding this comment

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

If I understand correctly this either (re)-generates everything, or strictly the ones you ask for (w. args)? Re-generating could lead to annoyance since you'd have to update test expectations, and re-generate screenshots too (I guess there's none of that currently, but still). Thinking out aloud, a small CLI might be neat, letting you select from the pre-defined options in REQUIRED_PACKAGES and what you want to generate and what not.

Copy link
Member Author

Choose a reason for hiding this comment

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

I imagine we will only be adding new snapshots rather than updating existing ones.... but happy for a PR to improve this 🙏

import { expect, test } from '@nuxt/test-utils/playwright'

test.describe('Create Command', () => {
// TODO: these tests depend on external npm registry API - we should add data fixtures
Copy link
Contributor

Choose a reason for hiding this comment

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

🚀

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

🧹 Nitpick comments (4)
CONTRIBUTING.md (2)

506-515: Consider adding language identifier to fenced code blocks.

Static analysis flagged this directory tree structure as missing a language specification. Adding text or an empty identifier would resolve the markdownlint warning.

📝 Suggested fix
-```
+```text
 test/fixtures/
 ├── npm-registry/

538-543: Consider adding language identifier to this code block.

Similar to the above, this error message example is flagged by markdownlint for missing a language specification.

📝 Suggested fix
-```
+```text
 UNMOCKED EXTERNAL API REQUEST DETECTED
test/fixtures/npm-registry/packuments/@vitejs/plugin-vue.json (1)

1359-1397: Consider trimming large/PII‑heavy fixture fields

These fixtures embed full README text and maintainer emails, which inflates the repo and retains personal data. If tests don’t assert on these fields, consider stripping them (e.g., readme/readmeFilename, _npmUser.email, maintainers[].email, dist.signatures/attestations) via the fixture generator to keep data lean.

modules/runtime/server/cache.ts (1)

142-149: Avoid real‑time timestamps in fixture responses.

Using current time for publishedAt/lastSynced can introduce non‑determinism in snapshot assertions. Consider a fixed fallback or derive solely from fixture data.

Possible adjustment
-  return {
-    data: {
-      name: packageName,
-      specifier,
-      version,
-      publishedAt: packument.time?.[version] || new Date().toISOString(),
-      lastSynced: Date.now(),
-    },
-  }
+  const publishedAt = packument.time?.[version] ?? '1970-01-01T00:00:00.000Z'
+  return {
+    data: {
+      name: packageName,
+      specifier,
+      version,
+      publishedAt,
+      lastSynced: 0,
+    },
+  }

Comment on lines 20 to 26
function packageToFixturePath(packageName: string): string {
if (packageName.startsWith('@')) {
const [scope, name] = packageName.slice(1).split('/')
return `npm-registry/packuments/@${scope}/${name}.json`
}
return `npm-registry/packuments/${packageName}.json`
}
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 | 🟡 Minor

🧩 Analysis chain

🏁 Script executed:

cat test/e2e/test-utils.ts

Repository: npmx-dev/npmx.dev

Length of output: 8218


Guard scoped package parsing to prevent undefined paths.

The function can receive scoped package names like @scope without a second segment when the preprocessing logic leaves it unchanged (in handleNpmRegistry when parts.length <= 2, or in handleFastNpmMeta when there is no version specifier). This causes name to become undefined, producing a path like npm-registry/packuments/@scope/undefined.json. Add validation to fail fast with a clear error message.

Suggested fix
   if (packageName.startsWith('@')) {
     const [scope, name] = packageName.slice(1).split('/')
+    if (!scope || !name) {
+      throw new Error(`Invalid scoped package name: ${packageName}`)
+    }
     return `npm-registry/packuments/@${scope}/${name}.json`
   }

Comment on lines 32 to 41
'nuxt',
'vue',
'lodash',
'vite',
'next',
'color',
'ufo',
'is-odd',
'date-fns',
'lodash.merge',
Copy link
Contributor

Choose a reason for hiding this comment

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

That list is a bit long, is there something unique about each and every item on this list?

Copy link
Member Author

Choose a reason for hiding this comment

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

no, we could probably cut it down. but does it hurt?

Copy link
Contributor

@wojtekmaj wojtekmaj Feb 3, 2026

Choose a reason for hiding this comment

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

Not really, but why have more code if we could have less. In particular, date-fns fixture is enormous. Just parsing JSON that is nearing 100k lines may become a performance bottleneck.

Copy link
Member Author

Choose a reason for hiding this comment

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

will see how far I can cut it down

Copy link
Member Author

Choose a reason for hiding this comment

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

updated - see what you think! ✅

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

🧹 Nitpick comments (2)
test/fixtures/npm-registry/packuments/vite.json (1)

1-12: Consider trimming the packument fixture to fields used in tests.
This snapshot is very large; pruning unused metadata keeps fixtures lighter and reduces update churn.

modules/runtime/server/cache.ts (1)

90-153: Prefer deterministic timestamps in fast‑npm‑meta mocks.
publishedAt and lastSynced fall back to current time, which can introduce non‑determinism if tests assert on these fields. Consider deriving from fixture metadata.

♻️ Suggested change
-  return {
-    data: {
-      name: packageName,
-      specifier,
-      version,
-      publishedAt: packument.time?.[version] || new Date().toISOString(),
-      lastSynced: Date.now(),
-    },
-  }
+  const publishedAt =
+    packument.time?.[version] ??
+    packument.time?.modified ??
+    '1970-01-01T00:00:00.000Z'
+
+  return {
+    data: {
+      name: packageName,
+      specifier,
+      version,
+      publishedAt,
+      lastSynced: Date.parse(publishedAt),
+    },
+  }

Copy link
Contributor

@wojtekmaj wojtekmaj left a comment

Choose a reason for hiding this comment

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

1/4 the size of the original fixtures, isn't that nice?! Well done!

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

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

@codecov
Copy link

codecov bot commented Feb 3, 2026

Codecov Report

✅ All modified and coverable lines are covered by tests.

📢 Thoughts on this report? Let us know!

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

🧹 Nitpick comments (3)
modules/runtime/server/cache.ts (2)

228-234: Consider using deterministic timestamps for fixture responses.

Lines 232-234 use new Date().toISOString() and Date.now() which introduce non-determinism. Whilst this may not affect most tests, it could cause issues in snapshot testing or when comparing cached values.

Suggested fix
   const result: Record<string, unknown> = {
     name: packageName,
     specifier,
     version,
-    publishedAt: packument.time?.[version] || new Date().toISOString(),
-    lastSynced: Date.now(),
+    publishedAt: packument.time?.[version] ?? '1970-01-01T00:00:00.000Z',
+    lastSynced: 0,
   }

411-582: Consider breaking down fetchFromFixtures into smaller helper functions.

At ~170 lines, this function handles many concerns: mock responses, fast-npm-meta, GitHub API, fixture matching, stub generation, and version resolution. Extracting helpers for each fixture type (e.g., handlePackumentFixture, handleOrgFixture) would improve readability and testability.

test/e2e/test-utils.ts (1)

325-365: Consider loading GitHub contributors from the fixture file.

The hardcoded contributor data here (3 contributors) differs from test/fixtures/github/contributors.json (5 contributors). This could cause inconsistencies between client-side Playwright mocks and server-side fixture responses. Loading from the fixture would ensure consistency.

Suggested fix
 async function handleGitHubApi(route: Route): Promise<boolean> {
   const url = new URL(route.request().url())
   const pathname = url.pathname

   // Contributors endpoint: /repos/{owner}/{repo}/contributors
   const contributorsMatch = pathname.match(/^\/repos\/([^/]+)\/([^/]+)\/contributors$/)
   if (contributorsMatch) {
+    const fixture = readFixture('github/contributors.json')
     await route.fulfill({
-      json: [
-        {
-          login: 'danielroe',
-          id: 28706372,
-          avatar_url: 'https://avatars.githubusercontent.com/u/28706372?v=4',
-          html_url: 'https://github.com/danielroe',
-          contributions: 150,
-        },
-        {
-          login: 'antfu',
-          id: 11247099,
-          avatar_url: 'https://avatars.githubusercontent.com/u/11247099?v=4',
-          html_url: 'https://github.com/antfu',
-          contributions: 120,
-        },
-        {
-          login: 'pi0',
-          id: 5158436,
-          avatar_url: 'https://avatars.githubusercontent.com/u/5158436?v=4',
-          html_url: 'https://github.com/pi0',
-          contributions: 100,
-        },
-      ],
+      json: fixture || [],
     })
     return true
   }

   return false
 }

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

🧹 Nitpick comments (2)
modules/runtime/server/cache.ts (2)

141-162: Consider using deterministic timestamps in mock responses.

Line 147 uses new Date().toISOString() which produces non-deterministic output. For fully reproducible CI tests, consider using a fixed timestamp.

Suggested change
       return {
         data: {
-          analyzedAt: new Date().toISOString(),
+          analyzedAt: '2025-01-01T00:00:00.000Z',
           collected: {

519-554: Stub packument uses non-deterministic timestamps.

Lines 533-535 use new Date().toISOString() for the stub packument's time fields. For fully deterministic CI tests, consider using fixed timestamps.

Suggested change
+      const stubTimestamp = '2025-01-01T00:00:00.000Z'
       const stubPackument = {
         'name': match.name,
         'dist-tags': { latest: stubVersion },
         'versions': {
           [stubVersion]: {
             name: match.name,
             version: stubVersion,
             description: `Stub fixture for ${match.name}`,
             dependencies: {},
           },
         },
         'time': {
-          created: new Date().toISOString(),
-          modified: new Date().toISOString(),
-          [stubVersion]: new Date().toISOString(),
+          created: stubTimestamp,
+          modified: stubTimestamp,
+          [stubVersion]: stubTimestamp,
         },
         'maintainers': [],
       }

Comment on lines +126 to +139
// Bundlephobia API - return mock size data
if (host === 'bundlephobia.com' && pathname === '/api/size') {
const packageSpec = searchParams.get('package')
if (packageSpec) {
return {
data: {
name: packageSpec.split('@')[0],
size: 12345,
gzip: 4567,
dependencyCount: 3,
},
}
}
}
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 | 🟡 Minor

Scoped package name parsing bug in bundlephobia mock.

For scoped packages like @scope/[email protected], split('@')[0] returns an empty string because the result is ['', 'scope/name', '1.0.0']. Use the existing parseScopedPackageWithVersion helper for consistency.

Proposed fix
   // Bundlephobia API - return mock size data
   if (host === 'bundlephobia.com' && pathname === '/api/size') {
     const packageSpec = searchParams.get('package')
     if (packageSpec) {
+      const parsed = parseScopedPackageWithVersion(packageSpec)
       return {
         data: {
-          name: packageSpec.split('@')[0],
+          name: parsed.name,
           size: 12345,
           gzip: 4567,
           dependencyCount: 3,
         },
       }
     }
   }

@danielroe danielroe merged commit c295d19 into main Feb 3, 2026
17 checks passed
@danielroe danielroe deleted the test/fixtures branch February 3, 2026 18:57
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

4 participants