mswjs / msw

Industry standard API mocking for JavaScript.
https://mswjs.io
MIT License
15.91k stars 517 forks source link

MSW leaves cypress component test runner in a loop #744

Open yannbf opened 3 years ago

yannbf commented 3 years ago

Describe the bug

Cypress 7 recently released an interesting feature called component test runner. It allows to mount a component and run it in isolation, rather than having to run the entire application. I was trying to use it with MSW (which would be awesome!), but for some reason, starting the service worker makes the test behave weirdly, becoming flaky, sometimes looping and causing trouble.

Environment

To Reproduce

Steps to reproduce the behavior:

before, check this repro: https://github.com/yannbf/msw-cypress-component-issue

  1. yarn
  2. yarn cy
  3. Select Login.test.jsx
  4. Check the comments in https://github.com/yannbf/msw-cypress-component-issue/blob/main/src/components/Login.test.jsx#L9. As soon as you remove the start of the service worker, the tests flow naturally. When you add them, there's something weird that results in cypress runner looping.

Expected behavior

MSW should not impact cypress

Screenshots

2021-05-22 at 23 00 45 - Amber Beaver

yannbf commented 3 years ago

The effect is related to the following code: https://github.com/mswjs/msw/blob/86c23ec16c10698eb329798e883980a9b9159f05/src/setupWorker/start/utils/getWorkerInstance.ts#L25-L33

commenting it out removes the loop and flaky behavior, but of course msw doesn't work as expected

kettanaito commented 3 years ago

Hey, @yannbf. Thanks for reporting this.

The question is why navigator.serviceWorker.controller becomes undefined?

It's highly recommended to have the MSW set up on the application's side. That way your app registers the worker, and Cypress loads your app that already has the mocking enabled. If you absolutely must have MSW integration on the Cypress side, I'd imagine it being set up in some global hook (i.e. before all test suites) as opposed to a single test's before.

yannbf commented 3 years ago

The question is why navigator.serviceWorker.controller becomes undefined?

It's highly recommended to have the MSW set up on the application's side. That way your app registers the worker, and Cypress loads your app that already has the mocking enabled. If you absolutely must have MSW integration on the Cypress side, I'd imagine it being set up in some global hook (i.e. before all test suites) as opposed to a single test's before.

Great one. Cypress component test runner does not rely on having an application anymore, as it bootstraps components in isolation. So indeed msw should be set somewhere at a higher level once. I tried setting it up in a support file (and updated the repro repo), and the result is that cypress fails (the test is just asserting if the element exists), then if I retry, it gets in an infinite loop.

image

yannbf commented 3 years ago

@lmiller1990 I heard you were looking into msw integration with CT. Do you think it's currently possible?

lmiller1990 commented 3 years ago

My first instinct was to do exactly what you are doing, and set it up inside of beforeEach (or before). I am also curious as to why navigator.serviceWorker.controller is undefined. I've used msw many times before (but not with Cypress) and not seen this before.

I don't see any reason why this isn't possible - but I also don't see an obvious reason why this isn't working in the first place. I am not sure I can prioritize this dive right now, but I would like to use msw in Cypress, so I'll try to explore this a bit more at some point in the near future. I'll follow this thread and post anything that comes to mind.

According to MDN:

The controller read-only property of the ServiceWorkerContainer interface returns a ServiceWorker object if its state is activating or activated (the same object returned by ServiceWorkerRegistration.active). This property returns null if the request is a force refresh (Shift + refresh) or if there is no active worker.

I wonder if we are doing a refresh somewhere on the Cypress end that would be causing the last part of this to occur.

Edit: created this issue for more visibility.

yannbf commented 3 years ago

Hey @lmiller1990 thanks a lot for helping out with this! I noticed that the service worker is only registered after running the test. I believe that ideally the service worker registration should happen as soon as cypress is bootstrapped.

Is there any way of running a "global setup" code that executes when cypress is open, without having to running any test suite? If we had a way to do that, I believe msw could work! As an example, in Storybook there is a preview.js file which executes once Storybook is bootstrapped. By setting msw in that file, it works across every story. The service worker is registered outside of the context of a story, and is optionally configured when running stories.

lmiller1990 commented 3 years ago

Normally we recommend using before or beforeEach for this - seems like a race condition.

edit: I see what you are doing now: https://github.com/yannbf/msw-cypress-component-issue/blob/74115f93f9cd7d5239b01d56501d6fbd248294dc/cypress/support/index.js. You can actually put a before or beforeEach hook in support/index.js. As long as it returns a Promise, Cypress will wait. So something like:

before(() => {
  return new Promise(res => {
    /* need some way to know when MSW has started, then resolve the promise */
    setupWorker(...).start()
  })
})

We need some way to know when MSW has started. Is there something like that? if start() returns a Promise, that would be perfect.

For future reference, Cypress does expose some event. You don't see them often, since you generally should just use before or beforeEach. The events are documented here. test:before:run and test:before:run might be useful. They work like Cypress.on('test:before:run', (attributes , test) => { /* stuff */ }).

kettanaito commented 3 years ago

@lmiller1990 calling worker.start() does return a Promise that indicates when the worker is activated and ready to process requests. You can await that promise in your before hook.

The navigator.serviceWorker.controller may be undefined because the worker is not registered in the origin where the code tries to resolve its controller. Cypress runs in its own context (and host), while the tested app has its own context (and that's where the worker is registered). We have a custom logic on the worker's side to look up the proper registration and respect it when working with cypress (support for registering the worker in a nested iframe to still affect the requests of the parent frame). It just may happen that accessing the controller in the parent frame doesn't resolve to the child frame's controller. This needs to be investigated. We have iframe integration tests where you can inspect how the controller behaves in the case of nested iframes:

I don't have the capacity to look into this at the moment but will keep an eye on this issue to help in whichever way I can. Thank you all for the discussion on this topic.

neviaumi commented 3 years ago

I not sure i run into exact some problems

I try integrate MSW with cypress components test


import { useAsyncFn } from "react-use"

export function TestApp({ children }) {
  const [
    {
      value: startedMockServer,
      loading: startMockServerLoading,
      error: startMockServerError,
    },
    startWorkerFunction,
  ] = useAsyncFn(async () => {
    await mockWorker.start({
      serviceWorker: {
        url: 'https://cdn.jsdelivr.net/npm/msw/lib/esm/mockServiceWorker.js',
      },
    });
    window.msw = {
      worker: mockWorker,
    };
    console.log('MockServiceWorker Init');
    return true;
  }, []);

  useEffect(() => {
    if (!startedMockServer && !startMockServerLoading) {
      startWorkerFunction();
    }
  }, [startedMockServer, startWorkerFunction, startMockServerLoading]);
  if (startMockServerLoading) return null;
  if (startMockServerError)
    throw new Error('Init TestApp error');

  return (
        <>{children}</>
  );
}

I attempt create TestApp for configure test environment first.

but it suck in mockWorker.start(), the promise just never return

I have attempt add some log to debugging

https://github.com/mswjs/msw/blob/9a43bc9cba49f3c18b1ab1ed700d3f41f9fe86f5/src/setupWorker/start/createStartHandler.ts#L70

Turn out i find i suck here, the worker just never response the event

kettanaito commented 3 years ago

I can confirm that the issue exists but have not enough insights to share as to its cause. Feel free to share your findings, it's a general network/javascript debugging that should take place to find out what goes wrong. Thank you for your patience.

hubgit commented 3 years ago

I've had some luck with the following approach:

cypress/support/msw.js:

import { setupWorker } from 'msw'

export const serviceWorker = setupWorker()

export const waitForServiceWorker = serviceWorker.start({
  serviceWorker: {
    url: '/mockServiceWorker.js',
  },
})

cypress/support/index.js:

import './msw'

src/components/example.spec.js:

import { serviceWorker, waitForServiceWorker } from '../../cypress/support/msw'
import { rest } from 'msw'

describe('example', function () {
  before(() => waitForServiceWorker)
  afterEach(() => serviceWorker.resetHandlers())

  it('makes a request', function () {
    serviceWorker.use(
      rest.post('/foo', (req, res, ctx) => {
        return res(
          ctx.status(200),
          ctx.json({ foo: 'bar' })
        )
      })
    )

    mount(<ComponentThatMakesARequest/>)
  })
})

I'm now hitting https://github.com/cypress-io/cypress/issues/14745 but that seems to be a separate issue - I can at least see the response being served by the msw ServiceWorker.

reintroducing commented 2 years ago

I'm trying to get this working as well but I can't even get past the fact that the mockServiceWorker.js file cannot be served during component testing. My app is running on localhost:3000 and the component tests run on localhost:8080. If I try to point it to localhost:3000/ i get the following:

serviceWorker: {
    url: 'http://localhost:3000/mockServiceWorker.js',
},

[MSW] Failed to register the Service Worker:

Failed to register a ServiceWorker: The origin of the provided scriptURL ('http://localhost:3000') does not match the current origin ('http://localhost:8080').

If I try to path it relatively (serviceWorker: {url: 'dist/public/mockServiceWorker.js'},) I get the following that The script has an unsupported MIME type ('text/html').

If I try to go from a base path I get the following:

serviceWorker: {url: '/dist/public/mockServiceWorker.js'},

[MSW] Failed to register a Service Worker for scope ('http://localhost:8080/') with script ('http://localhost:8080/dist/public/mockServiceWorker.js'): Service Worker script does not exist at the given path.

Did you forget to run "npx msw init <PUBLIC_DIR>"?

How can I get past this? I know that I need to have the service worker file alongside the server running on 8080 but I don't know where I can even copy that file to since Cypress is just kind of sandboxing this whole thing.

Any help would be appreciated.

kettanaito commented 2 years ago

Hey, @reintroducing. The worker script must always be served from the same hostname. You cannot have a worker at :3000 and handle requests that happen at :8000β€”that's against the security design of service workers and any browser will forbid this.

Your second approach is correct. Only you're pointing at a non-existing resource: there's nothing at dist/public/mockServiceWorker.js. You get the Mime type error because accessing that URL likely produces a 404 page (HTML page). You can try following that URL yourself and see where it leads.

My suspicion is that you need to omit the /dist part of your worker script URL. I assume that /dist is the public folder, which is the root of your application. I think even /public looks suspicious in the URL but you're the best to verify this.

  1. Find how your application serves the worker script. I suspect it's at http://localhost:8080/mockServiceWorker.js but I may be wrong.
  2. Use that (relative) URL as the custom worker URL in serviceWorker.url option to worker.start().
reintroducing commented 2 years ago

@kettanaito Thanks for your reply. My apologies, I left out the fact that I had tried all the possibly URLs I could think of at :8080 as well but I basically just get the ole Cannot GET /mockServiceWorker.js with the path changing based on what I was trying. The reason for this is because I don't know where in the filesystem Cypress is serving the files from and therefore I don't know where I should copy the mSW.js file into for it to be served accordingly alongside the component testing application. Here is an example URL of my App.spec (http://localhost:8080/__/#/tests/component/app/App.spec.js) but again, I can't figure out where its being served from as it pertains to the filesystem itself.

reintroducing commented 2 years ago

I was able to resolve my issue when I realized that webpack for the cypress component tests was being served on localhost:8080/public (whereas my app serves on localhost:3000/dist/public, long story, dont ask). Once i realized that I was able to copy the worker file into the /public directory and didn't need the file path in the worker.start call since its served from / by default.

vospascal commented 2 years ago

Any updates on this?

roryschadler commented 2 years ago

I'm having the same problem, and none of the above workarounds have worked for me. I'd really love to be able to use MSW in my Cypress tests, is there any progress on this issue?

sanghunlee-711 commented 2 years ago

Any updates on this? I'm having the same situation and even Cypress is updated a lot with new version. It's really hard to know cypress new versions problem or msw problem. Below is my procject's version. Please share some informations who has same situation with this issue.

"cypress": "^10.11.0",
"msw": "^0.47.4",
lmiller1990 commented 2 years ago

There is a minimal reproduction in the OP, we should update it to the latest version of everything and see if it's still happening. I suspect it's a config issue.

Also we have an issue in Cypress to track and work needed on our end: https://github.com/cypress-io/cypress/issues/16742

the-ult commented 1 year ago

I'm running in the same problem as well. And tried a lot of different (suggested) solutions. But none seem to work.

As soon as MSW is loaded, Cypress gets in some sort of reload loop.

Screenshot 2022-12-14 at 13 38 54

And I'm getting the following errors:

[MSW] Cannot intercept requests on this page because it's outside of the worker's scope ("http://localhost:8080/__cypress/src/"). If you wish to mock API requests on this page, you must resolve this scope issue.

- (Recommended) Register the worker at the root level ("/") of your application.
- Set the "Service-Worker-Allowed" response header to allow out-of-scope workers.

Which is correct. And setting the scope to '/' does not help.

Sometimes it correctly says: MSW Enabled, but the loop still happens.

Screenshot 2022-12-14 at 15 11 37


I'm trying to get MSW working in combination with Cypress Component tests in a Nx workspace (so I can write a blog about it). Running the application, with 'normal' Cypress tests and Jest tests with MSWjs all work perfectly πŸ‘πŸΌ

Example repo: https://github.com/the-ult/angular-nx-playground/. (Work in progress πŸ˜‡ )

And the tests are in:

Cypress Component Test setup for movie/feature-movies:

The mockServiceWorker.js is located in: https://github.com/the-ult/angular-nx-playground/tree/main/libs/shared/test/msw/src/assets

package.json

  "msw": {
    "workerDirectory": "libs/shared/test/msw/src/assets"
  },

And (automatically) served on: https://localhost:8080/__cypress/src/mockServiceWorker.js


Questions


Versions

Node : 18.12.1
OS   : darwin x64
pnpm : 7.18.2

nx : 15.3.3
@nrwl/angular : 15.3.3
@nrwl/cypress : 15.3.3
@nrwl/devkit : 15.3.3
@nrwl/eslint-plugin-nx : 15.3.3
@nrwl/jest : 15.3.3
@nrwl/js : 15.3.3
@nrwl/linter : 15.3.3
@nrwl/nx-cloud : 15.0.2
@nrwl/webpack : 15.3.3
@nrwl/workspace : 15.3.3
typescript : 4.8.4

cypress: 12.1.0
msw: 0.49.2 // Tried 0.0.0-fetch.rc-3 as well
lmiller1990 commented 1 year ago

@the-ult that is really interesting, I was working on something similar to this earlier: https://github.com/cypress-io/cypress/pull/25120

You say it's only happening in CT and not E2E - I guess what might be happen is that, instead of the request going to the main network layer, it heads to the dev server route instead, and this messes things up.

I'll try your repo. Do I just clone and run something? yarn cypress open maybe?

Also stupid question, but what does MSW do that cy.intercept() doesn't (this should be vastly more powerful, understandable if you just want to use MSW since you are migrating from Jest or something and don't want to touch your code too much).

reintroducing commented 1 year ago

Also stupid question, but what does MSW do that cy.intercept() doesn't (this should be vastly more powerful, understandable if you just want to use MSW since you are migrating from Jest or something and don't want to touch your code too much).

@lmiller1990 im not the poster but I can tell you why we use msw over cy.intercept in our app. We use msw extensively to mock just about every api during development so we don’t have to depend on a BE server to run alongside our FE app. Then, during tests, msw is utilized to mock those same calls so we never have to write a cy.intercept in our tests and the api calls β€œjust work”. It serves double duty!

lmiller1990 commented 1 year ago

@reintroducing thanks, I figured it was something like this - makes sense.

kettanaito commented 1 year ago

what does MSW do that cy.intercept() doesn't

@lmiller1990, cy.intercept() is bound to Cypress. MSW is framework-, tool-, and environment-agnostic. It's simply a better time investment that allows to reuse API mocks across the entire stack.

MSW also works completely differently compared to cy.intercept(), since MSW doesn't patch request clients or meddle with internal browser flags. It uses standard web APIs to allow requests to fully execute, intercepting them at the network level.

the-ult commented 1 year ago

Also stupid question, but what does MSW do that cy.intercept() doesn't (this should be vastly more powerful, understandable if you just want to use MSW since you are migrating from Jest or something and don't want to touch your code too much).

There are no stupid questions πŸ˜‰

In my previous project we were working with GraphQL. And intercepting GraphQL requests in earlier version of Cypress (pre-intercept) was a bit cumbersome. With MSWjs this was easy.

But the greatest 'win' of MSWjs is one-easy-way for API mocking for everything.

So for everything you can use the same API and easily re-use mock-data and Handlers. Without having to learn cy.intercept, jest Spy/Mock, etc.

(That's why I was trying to create this sample repo πŸ˜‡ )

I'll try your repo. Do I just clone and run something? yarn cypress open maybe?

Good question. I'll update the README.md and add install/run instructions.

I'm using pnpm

install

pnpm i

Run

App

pnpm start:msw movie-db

Jest Tests

pnpm test
// OR
nx test --runInBand

E2E

pnpm e2e:msw
// OR
nx e2e movie-db-e2e --watch --browser=chrome

Component Test πŸ”₯ FAILS πŸ”₯

pnpm exec nx run movie-feature-media-items:component-test --skip-nx-cache=true --watch=true --browser=chrome
// OR
nx run movies-feature-movies:component-test --watch=true --browser=chrome
lmiller1990 commented 1 year ago

Ok thanks @kettanaito and @the-ult. I'll poke around Cypress a bit, it probably won't be until next week though. I'm fairly sure something is getting muddled up in the routing for CT.

The CT specific routes are here https://github.com/cypress-io/cypress/blob/develop/packages/server/lib/routes-ct.ts The shared routes are https://github.com/cypress-io/cypress/blob/develop/packages/server/lib/routes.ts

Small files, so this should not be too hard to debug.

the-ult commented 1 year ago

Awesome. In de meantime, I'll try and fix the E2E and improve the documentation πŸ‘πŸΌ

And see if I can debug some myself as well

[edit] E2E is working again. Monday I'll improve the documentation

kettanaito commented 1 year ago

Thanks for all the involvement on this, folks! Let me know if I can be of any help. I should be available sometime at the beginning of the next year.

lydemann commented 1 year ago

Any news on this? This is a game changer once working.

the-ult commented 1 year ago

I updated my sample project a little bit. But did not get the component tests working. Haven't had time to try and debug the files @lmiller1990 mentioned.

Will try to give it another look next week

lydemann commented 1 year ago

Also seems the service worker is not proxied to the right Cypress asset location when requested:

image
kettanaito commented 1 year ago

Hey, folks. I'm sorry if this is blocking your development. As the only person maintaining this project, there's only that much I can do. Right now, I'm focusing on features and bug fixes that more people can benefit from (and even that takes a lot of time as I develop MSW in my free time). This is an open-source project so anybody can fork it, dive into the issue, debug it, and share their findings. I can then provide my expertise and help you merge a meaningful bugfix to solve it for everyone.

I'm trying to help where I can but I am a human and some things do get overlooked. There are also things that I have zero interest in and that I will likely never address. That's the balance that helps me keep this whole open source thing afloat.

@lydemann, that looks like you're not serving the worker script with Cypress for one reason or another. Double-check where you're initiating MSW and whether the worker is available at that path (most likely it's not, thus 404).

the-ult commented 1 year ago

Hey @kettanaito,

Definitely understand. And really appreciate all the work you are doing for this amazing project! πŸ‘πŸΌ

Haven't had time to dive any further in this issue. Hopefully I'll be able to make some time and effort next week (again πŸ˜‡ ).

lydemann commented 1 year ago

Hey, folks. I'm sorry if this is blocking your development. As the only person maintaining this project, there's only that much I can do. Right now, I'm focusing on features and bug fixes that more people can benefit from (and even that takes a lot of time as I develop MSW in my free time). This is an open-source project so anybody can fork it, dive into the issue, debug it, and share their findings. I can then provide my expertise and help you merge a meaningful bugfix to solve it for everyone.

I'm trying to help where I can but I am a human and some things do get overlooked. There are also things that I have zero interest in and that I will likely never address. That's the balance that helps me keep this whole open source thing afloat.

@lydemann, that looks like you're not serving the worker script with Cypress for one reason or another. Double-check where you're initiating MSW and whether the worker is available at that path (most likely it's not, thus 404).

It seems that it is not proxied correctly to /__cypress/src/mockServiceWorker.js by Cypress. I can still fetch the correct file with:

        worker.start({
            serviceWorker: {
                url: '/__cypress/src/mockServiceWorker.js',
            },
        });
lydemann commented 1 year ago

I made a video going through the specific problem:

https://www.loom.com/share/e200a7c9311d4b90af3899d04623ac0f

If anybody has any ideas on how to proceed from here it is much appreciated.

lmiller1990 commented 1 year ago

This works fine in my Vite example app: https://github.com/cypress-io/cypress-component-testing-apps/commit/d71e897e1c246f057a4148ad993acfd54660e020

In before, ensure you return - it's a promise, it needs to resolve and msw has to start before the test runs:

  before(() => {
    // return here! Don't forget.
    return worker.start();
  })

It doesn't seem to work in this user's Angular app, though: https://github.com/cypress-io/cypress/issues/26131

~I wonder if the problem lies in the cypress/webpack-dev-server somehow messing up the request.~ It seems to work fine with a simple webpack example I created, too.

lmiller1990 commented 1 year ago

@the-ult I am looking at your repro: https://github.com/mswjs/msw/issues/744#issuecomment-1351491630

How do I launch the component tests? I tried pnpx nx component-test movie-db but no luck.

the-ult commented 1 year ago

I added 1 component-test atm:

nx run movie-feature-media-items:component-test --watch

Updated the project a little. But the component-test is broken atm. (I'll try to make some time this week to fix the tests. )

Somehow the scope is incorrect. (What should it be?)

[MSW] Failed to register the Service Worker:

Failed to register a ServiceWorker for scope ('http://localhost:8080/') with script 
('http://localhost:8080/__cypress/src/mockServiceWorker.js'): 
The path of the provided scope ('/') is not under the max scope allowed 
('/__cypress/src/'). Adjust the scope, move the Service Worker script, 
or use the Service-Worker-Allowed HTTP header to allow the scope. 

(And have to figure out how we can run/show all component-tests.. There's probably a way for it.. Just haven't looked at it yet)

lmiller1990 commented 1 year ago

This is the exact problem I'm running into:

Failed to register a ServiceWorker for scope ('http://localhost:8080/') with script 
('http://localhost:8080/__cypress/src/mockServiceWorker.js'): 

It is only happening for Angular projects - all the others I've tried work fine. I think Angular's (incredibly complex) webpack config is routing the request for the mockServiceWorker.js somewhere incorrect. I know there's an API in msw to change where the mockServiceWorker.js is loaded from, I tried many different things using that, but still no dice.

the-ult commented 1 year ago

Yes indeed. I had it working (somehow) before. But then I got the loop.. Maybe @kettanaito could be of help?

Gonna try and give it another go tonight.

lmiller1990 commented 1 year ago

I do not think this is something @kettanaito can do, it seems like we need to do something in our own codebase. One other thing I'd try is verifying msw works in the browser outside of Cypress with Angular - to prove it's a Cypress x Angular webpack specific issue, which I suspect it is.

lydemann commented 1 year ago

I do not think this is something @kettanaito can do, it seems like we need to do something in our own codebase. One other thing I'd try is verifying msw works in the browser outside of Cypress with Angular - to prove it's a Cypress x Angular webpack specific issue, which I suspect it is.

MSW works just fine in a browser outside of Cypress :) It's within the component test things go in a loop.

lydemann commented 1 year ago

Any news? @lmiller1990

lmiller1990 commented 1 year ago

I spent a good chunk of time debugging this in https://github.com/cypress-io/cypress/issues/26131 but I didn't figure out the issue. It seems specific to webpack and Angular. 😒

lydemann commented 1 year ago

Damn that must be really tricky. No workarounds?

mct-dev commented 1 year ago

I am also having this same issue but with a NextJs project instead of Angular. If anyone discovers anything here let me know!

lydemann commented 1 year ago

Any more luck with this? @lmiller1990

lmiller1990 commented 1 year ago

Hey @lydemann - I'm sorry, I haven't looked at this issue since.

Jaesin commented 1 year ago

Ref: NextJS issues

I found when using basePath in nextConfig (e.g. /foo), you need to set serviceWorker.options.scope to /foo otherwise the scope will be set to /foo/ which wont match your root page.

    worker.start({
      serviceWorker: {
        options: { scope: '/foo' },
        url: '/foo/mockServiceWorker.js',
      },
    });

See: validateWorkerScope.ts. '/foo'.startsWith('/foo/') will always be false.

lydemann commented 1 year ago

Ref: NextJS issues

I found when using basePath in nextConfig (e.g. /foo), you need to set serviceWorker.options.scope to /foo otherwise the scope will be set to /foo/ which wont match your root page.

    worker.start({
      serviceWorker: {
        options: { scope: '/foo' },
        url: '/foo/mockServiceWorker.js',
      },
    });

See: validateWorkerScope.ts. '/foo'.startsWith('/foo/') will always be false.

Does this work for Angular as well?