Skip to content
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

Nuxt 3 CT Support #23619

Open
ZachJW34 opened this issue Aug 30, 2022 · 21 comments
Open

Nuxt 3 CT Support #23619

ZachJW34 opened this issue Aug 30, 2022 · 21 comments
Labels
CT Issue related to component testing Epic Requires breaking up into smaller issues prevent-stale mark an issue so it is ignored by stale[bot] type: enhancement Requested enhancement of existing feature

Comments

@ZachJW34
Copy link
Contributor

What would you like?

Support for Nuxt 3 Cypress Component Testing

Why is this needed?

We support Nuxt 2, and with Nuxt 3 on the horizon we should add support for Nuxt 3.

Other

There is a tech-brief with initial findings of Nuxt 3 support. There is also a Nuxt 3 issue with some interesting info on Nuxt 3 selective rendering which we might incorporate.

Would make sense to convert this issue into an epic once work beings.

@ZachJW34 ZachJW34 added the CT Issue related to component testing label Aug 30, 2022
@rockindahizzy rockindahizzy added Epic Requires breaking up into smaller issues stage: product backlog labels Aug 30, 2022
@joesobo
Copy link

joesobo commented Oct 31, 2022

Hey @ZachJW34! Was wondering if we could make the tech-brief you mentioned public? Would be nice to see your findings in the meantime

@ZachJW34
Copy link
Contributor Author

ZachJW34 commented Nov 1, 2022

It is a private document but contains no sensitive information so I'll copy and paste the contents below


Our current implementation of Nuxt 2 relies on accessing Nuxt's webpack config via an exposed getWebpackConfig function. Not only does this function no longer exist, but the Nuxt team has added Vite as it's primary bundler. Webpack can be used, but it is opt in via specifying vite: false in the nuxt.config.ts and installing @nuxt/webpack-builder.

Nuxt 3 internals

The majority of Nuxt 3 internals rely on building a global nuxt context that is delegated to each builder. This is facilitated via the loadNuxt function which can then be passed to a webpack or vite builder.

Vite

The source code related to sourcing and building a Nuxt 3 application using their Vite builder can be found here. This file resides in the @nuxt/vite-builder package.

There is no getViteConfig function that we can utilize since the sourcing and building occur in one function. There is a vite client config that extends from a shared viteConfig, but we might need the configuration from the base config (or we can provide it ourselves).

Webpack

The source code related to sourcing and building a Nuxt 3 application using their Webpack builder can be found here. This file resides in the @nuxt/webpack-builder. Webpack support is opt-in by specifying vite: false in the nuxt.config.ts and installing @nuxt/webpack-builder.

There is no getWebpackConfig function, which is what we used in our Nuxt 2 support. There is a webpack client config preset, but it relies on Nuxt internals and isn't a raw webpack config.

Global Components

A new Nuxt 3 feature is the automatic import of components/dependencies. For example, when generating a Nuxt 3 application with npx nuxi init nuxt-app, the app.vue file contains:

Notice there is no explicit import of the NuxtWelcome component. It also doesn't exist inside the project's source code. You can find it in node_modules/@nuxt/ui-templates/dist/templates/welcome.mjs where it is configured inside of [nuxt/core](https://github.com/nuxt/framework/blob/c95a48c63287aa7295edb36f4ff21e262ba12ecd/packages/nuxt/src/core/nuxt.ts) via their addComponent API.

We should support components that utilize this global dependency injection. Rendering the app.vue should work out of the box.

Trade-offs

The trade-off for pursing a solution that matches our Nuxt 2 implementation is that we are investing time in supporting a framework that we don't and haven't had full support for (SSR and server-side APIs).

Alternatives considered

Nuxt 3 offers a Module API that allows plugin authors to extend and tap into the build/serve lifecycle of a Nuxt 3 application. For example, there is a vite:extendConfig lifecycle that provides the client vite config for users (webpack:config for webpack users). Working within the Module API could provide us with the necessary extension points to add our Cypress logic.

For meta-frameworks like Next and Nuxt, we are grabbing their internals and plugging it up to our dev-server. There could be a solution where we plug our internals into their dev-server. This would be a drastic departure from our current solution.

Spike

I was able to get vue components rendering with our vite-dev-server by utilizing patch-package to make some slight modifications to the nuxt source code to allow us to grab a working vite configuration. I was not able to get an auto-imported component rendering, nor was I able to get the webpack version working. Sourcing the config (given the modifications to enable this) isn't too difficult, but modifying a complex Vite/Webpack configuration can be.

Nuxt 3 app with patches: https://github.com/ZachJW34/nuxt-3-cypress-ct Cypress branch with webpack/vite handlers: https://github.com/cypress-io/cypress/tree/zachw/nuxt-3-spike

References

Open issue for exposing Webpack/Vite Internals: nuxt/nuxt#14534

@lmiller1990
Copy link
Contributor

Also maybe useful: https://github.com/danielroe/nuxt-vitest

@lmiller1990
Copy link
Contributor

#25719

@lmiller1990
Copy link
Contributor

lmiller1990 commented Feb 6, 2023

Let's track all Nuxt related activity here. Update:

I am working on #25637 right now. This will let third parties slot their own integrations into Cypress.

Then, we can write a community plugin that makes Nuxt work. I'll try to do this in parallel with #25637.

Related: #24141

@floroz
Copy link

floroz commented Feb 6, 2023

Let's track all Nuxt related activity here. Update:

I am working on #25637 right now. This will let third parties slot their own integrations into Cypress.

Then, we can write a community plugin that makes Nuxt work. I'll try to do this in parallel with #25637.

Related: #24141

Thanks, @lmiller1990 for taking on this task and moving forward with the Cypress integration with Nuxt.

Please let me know if I can be of any help with the plugin (feel free to assign me/tag me in the issue once the public API for 3rd parties is implemented)

@lmiller1990
Copy link
Contributor

lmiller1990 commented Feb 6, 2023

Will do. I suspect we just need to figure out how to integrate Nuxt's Vite config - I know about as much as anyone else (not much) on this topic.

You should be able do it without the public API, it'll just be a bit messier. Something like

// cypress.config.ts
const { defineConfig } = require('cypress')

// https://github.com/nuxt/nuxt/issues/14534#issuecomment-1419025107
const { loadNuxt, buildNuxt } = require('@nuxt/kit');

// https://github.com/nuxt/framework/issues/6496
async function getViteConfig() {
  const nuxt = await loadNuxt({ cwd: process.cwd(), dev: false, ssr: false });
  return new Promise((resolve, reject) => {
    nuxt.hook('vite:extendConfig', (config) => {
      resolve(config);
      throw new Error('_stop_');
    });
    buildNuxt(nuxt).catch((err) => {
      if (!err.toString().includes('_stop_')) {
        reject(err);
      }
    });
  }).finally(() => nuxt.close());
}

module.exports = defineConfig({
  component: {
    devServer: {
      framework: 'vue',
      bundler: 'vite',
      viteConfig: async () => {
        const config = await getViteConfig();
        return config
      }
    },
  },
})

Note the ssr: false - this will NOT work work with SSR or server side hooks. This may even work (untested).

The Nuxt team does not plan to expose the functionality to kick off the server ref, so we will need to explore some strategies around this.

This should hopefully support things like Nuxt auto imports, etc.

@floroz
Copy link

floroz commented Feb 7, 2023

@lmiller1990 Yes, if you take a look at #25719 (comment) I had already tried that integration (you can find a minimal reproduction here).

I get the following error when starting Cypress:

Your configFile is invalid: /Users/floroz/Repository/base-nuxt3/cypress.config.ts

Stack Trace:

Error: Cannot find module 'file:///Users/floroz/Repository/base-nuxt3/cypress.config.ts'
Require stack:
- /Users/floroz/Library/Caches/Cypress/12.5.1/Cypress.app/Contents/Resources/app/packages/server/lib/plugins/child/run_require_async_child.js
- /Users/floroz/Library/Caches/Cypress/12.5.1/Cypress.app/Contents/Resources/app/packages/server/lib/plugins/child/require_async_child.js
    at Function.Module._resolveFilename (node:internal/modules/cjs/loader:995:15)
    at Function.Module._resolveFilename (/Users/floroz/Library/Caches/Cypress/12.5.1/Cypress.app/Contents/Resources/app/node_modules/tsconfig-paths/lib/register.js:75:40)
    at Function.Module._resolveFilename.sharedData.moduleResolveFilenameHook.installedValue [as _resolveFilename] (/Users/floroz/Library/Caches/Cypress/12.5.1/Cypress.app/Contents/Resources/app/node_modules/@cspotcode/source-map-support/source-map-support.js:811:30)
    at Function.Module._load (node:internal/modules/cjs/loader:841:27)
    at Module.require (node:internal/modules/cjs/loader:1061:19)
    at require (node:internal/modules/cjs/helpers:103:18)
    at /Users/floroz/Library/Caches/Cypress/12.5.1/Cypress.app/Contents/Resources/app/packages/server/lib/plugins/child/run_require_async_child.js:106:34
    at processTicksAndRejections (node:internal/process/task_queues:95:5)
    at async loadFile (/Users/floroz/Library/Caches/Cypress/12.5.1/Cypress.app/Contents/Resources/app/packages/server/lib/plugins/child/run_require_async_child.js:106:14)
    at async EventEmitter. (/Users/floroz/Library/Caches/Cypress/12.5.1/Cypress.app/Contents/Resources/app/packages/server/lib/plugins/child/run_require_async_child.js:116:32)

If I isolate the code, I could identify this line as the one causing the error

import { loadNuxt, buildNuxt } from "@nuxt/kit";

Here's the debugging gets a little hard... but somehow during the module resolution of @nuxt/kit something goes wrong.

I have also patched the @nuxt/kit/dist/index.mjs to contain noting but two empty functions loadNuxt and buildNuxt, but the same error gets thrown.

If I place some logs at the beginning of the file, I can see that that file is never executed.

Also running the debug flag

➜  nuxt3-starting-template git:(main) ✗ DEBUG=cypress:scaffold-config:detect yarn cypress open
yarn run v1.22.19
$ /Users/xxx/Repository/nuxt3-starting-template/node_modules/.bin/cypress open
  cypress:scaffold-config:detect Checking for default Cypress config file +0ms
  cypress:scaffold-config:detect Detected cypress.config.ts - using TS +1ms

@floroz
Copy link

floroz commented Feb 7, 2023

Okay, a few updates after investigating the issue a bit more in depth:

1. adding "type": "module" to the project package.json solved the config resolution issue.

@lmiller1990 you might know better whether this can be resolved within the cypress config, rather than adding the module to the package json.

2. The following setup lets you start the component testing

 viteConfig: async () => {
        console.log("log:entering viteconfig");
        await getViteConfig();
        return {};
      },

This validates that the nuxt object is loaded correctly and the config is resolved.
However, since no vite config is provided, the necessary vue plugin that is required fails the tests from running.

3. Loading the vite config in the cypress config is now yielding the following error:

Error: Cannot call server.listen in middleware mode.

Screenshot 2023-02-07 at 10 28 31

Found some info here: vitejs/vite#2094

4. Disable Middleware Mode ?

If I return the following config, I can load the Cypress tests.

return {
          ...config,
          server: {
            middlewareMode: false,
          },
        };

Screenshot 2023-02-07 at 10 35 04

However, the mounting doesn't happen correctly.

@xtoolkit
Copy link

xtoolkit commented Feb 24, 2023

hi
i following #23619 (comment) and try to setup components test for nuxt3. (tnx @floroz)

if we remove replace plugin from vite config, components can mount!

async viteConfig() {
  const config = await getNuxtConfig();

  config.plugins = config.plugins.filter(
    item => !['replace', 'vite-plugin-eslint'].includes(item.name)
  );

  config.server.middlewareMode = false;

  return config;
}

All good! but one problem in here. nuxt not install vue plugins... and we can't use top level async component...

@xtoolkit
Copy link

Hello again
I could do it (tnx @floroz). nuxt/nuxt#19304 (comment)

@lmiller1990
Copy link
Contributor

^ Comment is very useful - we can use this if/when we decide to add Nuxt 3 support.

@floroz
Copy link

floroz commented Feb 28, 2023

Amazing job @xtoolkit cracking the solution :)

Could you help me understand this line below? How did you identify those two plugins being responsible for failing the .mount(Component)?

 config.plugins = config.plugins.filter(
    item => !['replace', 'vite-plugin-eslint'].includes(item.name)
  );

@xtoolkit
Copy link

Problem is replace plugin. Remove vite-plugin-eslint is optional for better test develop DX.
I don't know why but problem at this line: https://github.com/nuxt/nuxt/blob/503b7acd554fd90949d7c58ca35a6d796b42542f/packages/vite/src/vite.ts#L79

@lmiller1990
Copy link
Contributor

Related: vuejs/test-utils#2119 (comment)

Is anyone using Nuxt and Cypress (or Vitest)? How is your experience? It looks like with the right wiring it should mostly work, but I haven't seen any comprehensive examples yet. You won't get SSR, but you should get the Nuxt auto imports like $fetch, etc.

@jrutila
Copy link

jrutila commented Jul 10, 2023

Hello, I am using Nuxt and Cypress to do component testing. I had to do some boilerplate manual work to get it working. Here's my cypress/support/component.ts if it is any help. There are probably some unnecessary bits already, but at least it is working.

import "./commands"

import { mount } from "cypress/vue"
import { getContext } from "unctx"
import { Suspense, defineComponent, h } from "vue"
import { createRouter, createMemoryHistory, RouteRecordName } from "vue-router"
// import VueRouter from "vue-router"

import vuetify from "../plugins/vuetify"
import MarkdownPlugin from "../../plugins/markdownit"

export const nghBaseUrl = "http://ngh-mock.test/ngh/"
export const searchBaseUrl = "http://ngh-mock.test/search/"

// Context mocking for cypress component tests
const nuxtAppCtx = getContext("nuxt-app")
const generateNuxtCTX = (): Record<string, any> => ({
  static: { data: {} },
  payload: { data: {}, _errors: {} },
  hook: () => () => ({}),
  hooks: {
    callHook: () => Promise.resolve(),
  },
  _asyncData: {},
  _asyncDataPromises: {},
  _useHead: () => ({}),
  _route: {},
  _router: {},
  $router: {},
  router: {},
  $config: { public: { nghBaseUrl, searchBaseUrl } },
})
const nuxtCTX = generateNuxtCTX()

nuxtAppCtx.set(nuxtCTX)

nuxtCTX.$md = MarkdownPlugin().provide.md
// Context mocking ends

const scheduler = typeof setImmediate === "function" ? setImmediate : setTimeout

export function flushPromises(): Promise<void> {
  return new Promise((resolve) => {
    scheduler(resolve, 0)
  })
}

export function wrapInSuspense(
  component: ReturnType<typeof defineComponent>,
  { props, slots }: { props: object; slots: object }
): ReturnType<typeof defineComponent> {
  return defineComponent({
    render() {
      return h(
        "div",
        { id: "root" },
        h(Suspense, null, {
          default() {
            return h(component, props, slots)
          },
          fallback: h("div", "Component test fallback"),
        })
      )
    },
  })
}

// Cypress.Commands.add('mount', mount)
declare global {
  namespace Cypress {
    interface Chainable {
      mount: typeof mount
    }
  }
}

Cypress.Commands.add("mount", (MountedComponent, options) => {
  options = options || {}
  options.global = options.global || {}
  // Add vuetify to make V-components work
  options.global.plugins = [vuetify]
  // Do not stub transitions
  options.global.stubs = { transition: false }
  options.global.mocks = options.global.mocks || {}
  options.global.provide = options.global.provide || {}

  // mocking the $fetch method response
  // eslint-disable-next-line require-await
  const fetchMethod = async (_url) => ({
    "hello": "world"
  })

  const fetchPlugin = {
    install(_app, _options) {
      globalThis.$fetch = fetchMethod
    },
  }

  const router = createRouter({
    routes: [
      {
        name:
          (options.global.mocks.route?.name as RouteRecordName) || "testRoute",
        path: "/x/:gameId/:guideId*",
        redirect: "",
      },
    ],
    history: createMemoryHistory(),
  })

  options.global.plugins.push(router)
  options.global.plugins.push(fetchPlugin)
  nuxtCTX.$router = router

  if (!options.global.mocks.$route) {
    options.global.mocks.$route = { name: "testRoute" }
  }

  if (options.global.mocks?.$route) {
    nuxtCTX._route = options.global.mocks.$route
    /*
    const $route = options.global.mocks?.route
    options.global.mocks.$route = $route


    nuxtCTX.router = router
    nuxtCTX.$router = router
    */
    // delete options.global.mocks.route
  }

  options.global.provide.nghBaseUrl =
    options.global.provide.nghBaseUrl || nghBaseUrl

  return mount(
    wrapInSuspense(MountedComponent, {
      props: options.props,
      slots: options.slots,
    }),
    options
  )
})

I would be interested in this topic. Was there some branch to test or make pull requests to?

@lmiller1990
Copy link
Contributor

lmiller1990 commented Jul 10, 2023

That is a heck of a lot of boilerplate. Lots of stubbing of Nuxt. I'd like to try this out and see how the experience is.

No branch right now - I think the best step is playing around in a separate, personal repo and smoothing things out. If we get to a point we are happy with, we can consider releasing something under the Cypress org.

@ASoldo
Copy link

ASoldo commented Mar 5, 2024

Are there any indication of progress on this topic for Nuxt3 component testing?
So far I'm in combo with cypress (e2e) and vitest (component).

@cypress-app-bot

This comment was marked as outdated.

@cypress-app-bot cypress-app-bot added the stale no activity on this issue for a long period label Sep 2, 2024
@ASoldo
Copy link

ASoldo commented Sep 2, 2024

Not stale, still waiting!

@cypress-app-bot cypress-app-bot removed the stale no activity on this issue for a long period label Sep 3, 2024
@jennifer-shehane jennifer-shehane added prevent-stale mark an issue so it is ignored by stale[bot] type: enhancement Requested enhancement of existing feature labels Sep 3, 2024
@guifeliper
Copy link

waiting... 🥺

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
CT Issue related to component testing Epic Requires breaking up into smaller issues prevent-stale mark an issue so it is ignored by stale[bot] type: enhancement Requested enhancement of existing feature
Projects
None yet
Development

No branches or pull requests