test-runner icon indicating copy to clipboard operation
test-runner copied to clipboard

[Feature Request] Support stories that override the user agent

Open notclive opened this issue 2 years ago • 4 comments

I have a component that behaves differently on different browsers / operating systems. I want to test these different behaviours.

I use a decorator that simulates the different environments by overridding the user agent:
export function givenUserAgent (userAgent: string) {
  function overrideUserAgent (userAgent: string) {
    Object.defineProperty(window.navigator, 'userAgent', {
      get: () => userAgent,
      configurable: true
    })
  }

  return function SetUserAgent (Story: StoryFn<any>) {
    const [isReady, setIsReady] = useReducer(() => true, false)

    useEffect(() => {
      const initialUserAgent = window.navigator.userAgent
      overrideUserAgent(userAgent)
      setIsReady()
      return function restoreUserAgent () {
        overrideUserAgent(initialUserAgent)
      }
    }, [])

    return <>
      {isReady && <Story />}
    </>
  }
}

But test-runner overrides the user agent itself in setup-page.ts https://github.com/storybookjs/test-runner/blob/428f7db84150abce3f9a0e69e40b98bed1f78f5a/src/setup-page.ts#L230

Because it does so without setting configurable: true when my decorator tries to override the user agent I get an error.

Message:
     Cannot redefine property: userAgent

      at __throwError (<anonymous>:180:15)
      at eval (eval at evaluate (:191:30), <anonymous>:4:19)
      at UtilityScript.evaluate (<anonymous>:193:17)
      at UtilityScript.<anonymous> (<anonymous>:1:44)

notclive avatar May 07 '23 13:05 notclive

I worked around this by overriding the whole navigator

  function overrideUserAgent (userAgent: string) {
    const previousNavigator = window.navigator
    // storybookjs/test-runner overrides the userAgent and doesn't set `configurable: true`.
    // To workaround this I override the whole navigator.
    Object.defineProperty(window, 'navigator', {
      get: () => Object.create(previousNavigator, {
        userAgent: {
          get: () => userAgent,
          configurable: true
        },
        // ua-parser-js throws TypeError unless this is overridden too.
        userAgentData: {
          get: () => undefined,
          configurable: true
        }
      }),
      configurable: true
    })
  }

notclive avatar May 07 '23 14:05 notclive

Hey there @notclive! Could you make a PR to fix this (adding configurable to the property definition)? I'm happy to take a look!

yannbf avatar May 10 '23 12:05 yannbf

I wasn't sure whether that was a desirable solution. Currently test-runner seems to depend on the user-agent changing (https://github.com/storybookjs/test-runner#storybooktestrunner-user-agent). Though, I'm not sure if any core behaviour depends on it, I haven't seen any issues from overriding.

notclive avatar May 11 '23 08:05 notclive

The test-runner doesn't really depend on the user-agent per se, it just provides it so that users can check against it for various purposes: https://github.com/storybookjs/test-runner#storybooktestrunner-user-agent

yannbf avatar Sep 19 '23 10:09 yannbf