diff --git a/README.md b/README.md
--- a/README.md
+++ b/README.md
@@ -18,6 +18,10 @@
 client = gen @API
 ```
 
+## Support
+
+TypeScript code generated for HTTP uses the `fetch` api, which works in Browsers and Nodejs. TypeScript code generated for WebSocket's only works in Browsers for the moment, as Nodejs doesn't have WebSocket support. Round trips are tested with Selenium using Chromium.
+
 ## Example
 
 You can see many examples in the tests. One provided here for documentation purposes:
@@ -27,7 +31,9 @@
   deriving stock (Generic)
   deriving anyclass (ToJSON, FromJSON, TypeScriptDefinition)
 
-type API = "foo" :> Capture "bar" Int :> Post '[JSON] Foo
+type API
+     = "foo" :> Capture "bar" Int :> Post '[JSON] Foo
+  :<|> "qux" :> WebSocketConduit String Foo
 
 client = tsClient @'[Foo] @API
 ```
@@ -43,11 +49,25 @@
 }
 const API = {
   base: "",
-  "/foo/:bar": (bar:number): Promise<Foo> => {
+  "/foo/:bar": (bar: number): Promise<Foo> => {
     const uri = `${API.base}/foo/${bar}`;
     return fetch(uri, {
       method: "POST"
     }).then(res => res.json());
+  },
+  "/qux": ():
+    Promise<{ send : (input: string) => void
+            , receive : (cb: (output: Foo) => void) => void
+            , raw : WebSocket
+    }> => {
+      const pr = window.location.protocol === "http:" ? "ws:" : "wss:";
+      const ws = new WebSocket(`${pr}//${window.location.host}${API.base}/foo/bar`);
+      return Promise.resolve({
+        send: (input: User) => ws.send(JSON.stringify(input)),
+        receive: (cb: ((output: User) => void)) =>
+          ws.onmessage = (message: MessageEvent<string>) => cb(JSON.parse(message.data)),
+        raw: ws
+      });
   }
 };
 ```
diff --git a/servant-aeson-generics-typescript.cabal b/servant-aeson-generics-typescript.cabal
--- a/servant-aeson-generics-typescript.cabal
+++ b/servant-aeson-generics-typescript.cabal
@@ -1,6 +1,6 @@
 cabal-version:      3.0
 name:               servant-aeson-generics-typescript
-version:            0.0.0.1
+version:            0.0.0.2
 synopsis:           Generates a TypeScript client for Servant APIs
 description:
   This project leveratges aeson-generics-typescript to generate type safe API bindings in TypeScript for a given Servant API.
@@ -71,6 +71,7 @@
     , http-types
     , jose-jwt
     , servant
+    , servant-websockets
     , string-interpolate
     , text
 
@@ -91,6 +92,7 @@
     , async                      >=2.2.4  && <2.3
     , base                       >=4.17.2 && <4.18
     , bytestring                 >=0.11.5 && <0.12
+    , conduit                    >=1.3.5  && <1.4
     , containers                 >=0.6.7  && <0.7
     , directory                  >=1.3.7  && <1.4
     , filepath                   >=1.4.2  && <1.5
@@ -104,11 +106,13 @@
     , servant                    >=0.20.1 && <0.21
     , servant-auth               >=0.4.1  && <0.5
     , servant-server             >=0.20   && <0.21
+    , servant-websockets         >=2.0.0  && <2.1
     , split                      >=0.2.4  && <0.3
     , string-interpolate         >=0.3.2  && <0.4
     , text                       >=2.0.2  && <2.1
     , time                       >=1.12.2 && <1.13
     , warp                       >=3.3.30 && <3.4
+    , webdriver                  >=0.12.0 && <0.13
 
   other-modules:
     Servant.Client.TypeScript
diff --git a/src/Servant/Client/TypeScript.hs b/src/Servant/Client/TypeScript.hs
--- a/src/Servant/Client/TypeScript.hs
+++ b/src/Servant/Client/TypeScript.hs
@@ -1,5 +1,6 @@
 {-# LANGUAGE QuasiQuotes #-}
 {-# OPTIONS_GHC -Wno-orphans #-}
+{-# OPTIONS_GHC -Wno-missing-methods #-}
 
 module Servant.Client.TypeScript
   ( -- * Generator functions
@@ -7,7 +8,7 @@
   , tsClient
     -- * Type Classes
   , Fletch (..)
-  , GenAll
+  , GenAll (..)
     -- * AST data types
   , DocType (..)
   , InputMethod (..)
@@ -35,7 +36,13 @@
 import           Data.Text (Text, pack, unpack)
 import           Data.Text.Encoding (decodeUtf8')
 import           Data.Typeable (Proxy (..))
-import           GHC.TypeLits (KnownSymbol, Symbol, symbolVal)
+import           GHC.TypeLits
+  ( ErrorMessage (Text)
+  , KnownSymbol
+  , Symbol
+  , TypeError
+  , symbolVal
+  )
 import           Jose.Jwt (Jwt)
 import           Network.HTTP.Types (Method, urlEncode)
 import           Servant.API as Servant
@@ -61,6 +68,7 @@
   , type (:>)
   , (:<|>)
   )
+import           Servant.API.WebSocketConduit (WebSocketConduit)
 
 hush :: Either a b -> Maybe b
 hush (Right x) = Just x
@@ -75,18 +83,22 @@
   | Header_
   | Body
   | Fragment
+  | WSInput
+  deriving stock (Eq, Show)
 
 -- | What kind of API documentation are we using for this route?
 type DocType :: Type
 data DocType
   = Summary'
   | Description'
+  deriving stock (Show)
 
 -- | An input chunk of the URI
 type URIBit :: Type
 data URIBit = PathBit String
             | ArgBit InputMethod String String
             | DocBit DocType String
+  deriving stock (Show)
 
 -- | Type class that iterates over the servant type and seperates out inputs and outputs
 type Fletch :: Type -> Constraint
@@ -102,10 +114,13 @@
   argBits = DocBit Description' (symbolVal $ Proxy @s) : argBits @xs
   returnType = returnType @xs
 
-instance Fletch EmptyAPI where
-  argBits = []
-  returnType = error "EmptyAPI's cannot return a value"
+instance (TypeError ('Text "💠 EmptyAPI's cannot be Fletched as they do not make a request")) => Fletch EmptyAPI where
+instance (TypeError ('Text "💠 EmptyAPI's cannot be GenAll as they do not make a request")) => GenAll EmptyAPI where
 
+instance (FieldTypeName i, FieldTypeName o) => Fletch (WebSocketConduit i o) where
+  argBits = [ArgBit WSInput "WebSocketInput" . fs_wrapped . fieldTypeName $ Proxy @i]
+  returnType = ("connect", fs_wrapped . fieldTypeName $ Proxy @o)
+
 instance (Fletch xs, KnownSymbol s) => Fletch ((s :: Symbol) :> xs) where
   argBits = PathBit (symbolVal (Proxy @s)) : argBits @xs
   returnType = returnType @xs
@@ -158,19 +173,34 @@
   '-' -> '_'
   x -> x
 
-genOne :: forall api. Fletch api => String
-genOne = [i|#{docs}"#{uriKey}": (#{functionArgs}): Promise<#{res}> => {
-    const uri = `${API.base}#{pathWithCaptureArgs}#{queryParams}`;
-    return fetch(uri, {
-      method: "#{method}"#{headers}#{reqBody}
-    }).then(res => res.json());
-  }|]
-  where
-    args = argBits @api
-
-    urlEncode' = hush . fmap unpack . decodeUtf8' . urlEncode True . fromString
+uriKey :: forall api. Fletch api => String
+uriKey = mconcat
+  [ "/"
+  , intercalate "/" $ marge @api \case
+          PathBit s -> Just s
+          ArgBit Capture doc _ -> mappend ":" <$> urlEncode' doc
+          _ -> Nothing
+  , let queries = marge @api \case
+          ArgBit Query doc _ -> urlEncode' doc
+          ArgBit Querys doc _ -> urlEncode' doc
+          _ -> Nothing
+     in if null queries then mempty else '?' : intercalate "&" queries
+  , let req = marge @api \case
+          ArgBit Body doc _ -> Just doc
+          _ -> Nothing
+     in if null req then mempty else "(" <> mconcat req <> ")"
+  , let hs = marge @api \case
+          ArgBit Header_ doc _ -> Just doc
+          _ -> Nothing
+    in if null hs then mempty else "{" <> intercalate "," hs <> "}"
+  , let frag = marge @api \case
+          ArgBit Fragment _ _ -> Just ()
+          _ -> Nothing
+     in if null frag then mempty else "#fragment"
+  ]
 
-    docs = mconcat $ marge \case
+docs :: forall api. Fletch api => String
+docs = mconcat $ marge @api \case
       DocBit Summary' s -> Just $ '/' : '/' : ' ' : s <> "\n  "
       DocBit Description' d -> Just [i|/*
    * #{d}
@@ -178,74 +208,96 @@
   |]
       _ -> Nothing
 
-    uriKey = let
-
-        pathWithCapture = intercalate "/" $ marge \case
-          PathBit s -> Just s
-          ArgBit Capture doc _ -> mappend ":" <$> urlEncode' doc
-          _ -> Nothing
-
-        pathQueryParams = if null queries then mempty else '?' : intercalate "&" queries
-          where
-          queries = marge \case
-            ArgBit Query doc _ -> urlEncode' doc
-            ArgBit Querys doc _ -> urlEncode' doc
-            _ -> Nothing
-
-        pathHeaders = if null hs then mempty else "{" <> intercalate "," hs <> "}"
-          where
-          hs = marge \case
-            ArgBit Header_ doc _ -> Just doc
-            _ -> Nothing
+urlEncode' :: String -> Maybe String
+urlEncode' = hush . fmap unpack . decodeUtf8' . urlEncode True . fromString
 
-        pathReqBody = if null req then mempty else "(" <> mconcat req <> ")"
-          where
-          req = marge \case
-            ArgBit Body doc _ -> Just doc
-            _ -> Nothing
+genWebSocket :: forall api. Fletch api => String
+genWebSocket = [i|#{docs @api}"#{uriKey @api}": (#{functionArgs @api}):
+    Promise<{ send : (input: #{wsInput}) => void
+            , receive : (cb: (output: #{wsOutput}) => void) => void
+            , raw : WebSocket
+    }> => {
+      const pr = window.location.protocol === "http:" ? "ws:" : "wss:";
+      const ws = new WebSocket(`${pr}//${window.location.host}${API.base}#{pathWithCaptureArgs @api}#{queryParams @api}`#{headersWS @api});
+      return Promise.resolve({
+        send: (input: #{wsInput}) => ws.send(JSON.stringify(input)),
+        receive: (cb: ((output: #{wsOutput}) => void)) =>
+          ws.onmessage = (message: MessageEvent<string>) => cb(JSON.parse(message.data)),
+        raw: ws
+      });
+  }|]
+  where
+    wsInput = case filter (\case
+      ArgBit WSInput _ _ -> True
+      _ -> False) $ argBits @api of
+      [ArgBit WSInput _ x] -> x
+      x                    -> error $ "Bad argits: " <> show x <> " This is a hack to transfer the input of the WebSocket to the function here."
+    (_, wsOutput) = returnType @api
 
-      in "/" <> pathWithCapture <> pathQueryParams <> pathReqBody <> pathHeaders
+genHTTP :: forall api. Fletch api => String
+genHTTP = [i|#{docs @api}"#{uriKey @api}": async (#{functionArgs @api}): Promise<#{res}> => {
+    const uri = `${API.base}#{pathWithCaptureArgs @api}#{queryParams @api}`;
+    return fetch(uri, {
+      method: "#{method}"#{headers @api}#{reqBody @api}
+    }).then(res => res.json());
+  }|]
+  where (method, res) = returnType @api
 
-    queryParams = (\x -> if null x then x else "?" <> x) . intercalate "&" $ marge \case
-      ArgBit Query doc _ -> (<> "=${" <> fromString (encodeJSVar doc) <> "}") <$> urlEncode' doc
-      ArgBit Querys doc _ -> do
-        doc' <- urlEncode' doc
-        let var = encodeJSVar doc
-        return [i|${#{var}.reduceRight((acc,x) => "#{doc'}=" + x + (acc ? "&" + acc : ""), "")}|]
-      _ -> Nothing
+queryParams :: forall api. Fletch api => String
+queryParams = (\x -> if null x then x else "?" <> x) . intercalate "&" $ marge @api \case
+  ArgBit Query doc _ -> (<> "=${" <> fromString (encodeJSVar doc) <> "}") <$> urlEncode' doc
+  ArgBit Querys doc _ -> do
+    doc' <- urlEncode' doc
+    let var = encodeJSVar doc
+    return [i|${#{var}.reduceRight((acc,x) => "#{doc'}=" + x + (acc ? "&" + acc : ""), "")}|]
+  _ -> Nothing
 
-    pathWithCaptureArgs = mappend "/" . intercalate "/" $ marge \case
-      PathBit s -> Just s
-      ArgBit Capture doc _ -> Just $ "${" <> encodeJSVar doc <> "}"
-      _ -> Nothing
+pathWithCaptureArgs :: forall api. Fletch api => String
+pathWithCaptureArgs = mappend "/" . intercalate "/" $ marge @api \case
+  PathBit s -> Just s
+  ArgBit Capture doc _ -> Just $ "${" <> encodeJSVar doc <> "}"
+  _ -> Nothing
 
-    headers = let
-      hs = intercalate ",\n        " $ marge \case
-        ArgBit Header_ doc "string" -> Just $ "\"" <> doc <> "\": " <> encodeJSVar doc
-        ArgBit Header_ doc _ -> Just $ "\"" <> doc <> "\": \"\" + " <> encodeJSVar doc
-        ArgBit Body _ _ -> Just "'Content-Type': 'application/json'"
-        _ -> Nothing
-      in if null hs then ("" :: String) else [i|,
+headers :: forall api. Fletch api => String
+headers = let
+  hs = intercalate ",\n        " $ marge @api \case
+    ArgBit Header_ doc "string" -> Just $ "\"" <> doc <> "\": " <> encodeJSVar doc
+    ArgBit Header_ doc _ -> Just $ "\"" <> doc <> "\": \"\" + " <> encodeJSVar doc
+    ArgBit Body _ _ -> Just "'Content-Type': 'application/json'"
+    _ -> Nothing
+  in if null hs then ("" :: String) else [i|,
       headers: {
         #{hs}
       }|]
 
-    reqBody = case
-      marge \case
-        ArgBit Body doc _ -> Just $ encodeJSVar doc
-        _ -> Nothing
-      of [doc] -> [i|,
-      body: JSON.stringify(#{doc})|]
-         _ -> ("" :: String)
+headersWS :: forall api. Fletch api => String
+headersWS = let
+  hs = intercalate ", " $ marge @api \case
+    ArgBit Header_ doc "string" -> Just $ encodeJSVar doc
+    ArgBit Header_ doc _ -> Just $ "\"\" + " <> encodeJSVar doc
+    _ -> Nothing
+  in if null hs then ("" :: String) else [i|, [#{hs}]|]
 
-    functionArgs = intercalate "," $ marge \case
-      ArgBit _ doc x -> Just $ encodeJSVar doc <> ":" <> x
-      _ -> Nothing
+reqBody :: forall api. Fletch api => String
+reqBody = case
+  marge @api \case
+    ArgBit Body doc _ -> Just $ encodeJSVar doc
+    _ -> Nothing
+  of [doc] -> [i|,
+      body: JSON.stringify(#{doc})|]
+     _ -> ("" :: String)
 
-    (method, res) = returnType @api
+functionArgs :: forall api. Fletch api => String
+functionArgs = intercalate "," $ marge @api \case
+  ArgBit y doc x
+    -- Fragments are not captured by a servant server, useful with Link
+    | y /= Fragment
+    -- WSInput is used to in .send, and not required as initial arguments
+    && y /= WSInput -> Just $ encodeJSVar doc <> ":" <> x
+  _ -> Nothing
 
-    marge :: forall b. (URIBit -> Maybe b) -> [b]
-    marge = flip mapMaybe args
+marge :: forall api b. Fletch api => (URIBit -> Maybe b) -> [b]
+marge = flip mapMaybe $ argBits @api
 
 -- | Obtain the String for the client a la carte without type definitions
 gen :: forall (api :: Type).
@@ -261,11 +313,21 @@
 class GenAll a where
   genAll :: String
 
+-- | Handle left association of routes (IE parens on the left)
+instance {-# OVERLAPS #-} (GenAll (route :<|> subrest), GenAll rest) => GenAll ((route :<|> subrest) :<|> rest) where
+  genAll = genAll @(route :<|> subrest) <> ",\n" <> genAll @rest
+
+-- | Handle right association of routes (IE parens on the left)
 instance (Fletch route, GenAll rest) => GenAll (route :<|> rest) where
-  genAll = genOne @route <> ",\n" <> genAll @rest
+  genAll = genAll @route <> ",\n" <> genAll @rest
 
+-- | Handle right association of routes (IE parens on the left)
 instance {-# OVERLAPPABLE #-} Fletch route => GenAll route where
-  genAll = genOne @route
+  genAll =
+    case fst $ returnType @route of
+      -- POS sentinel value until I think of something better
+      "connect" -> genWebSocket @route
+      _         -> genHTTP @route
 
 type TypeDecls :: [Type] -> Constraint
 class TypeDecls xs where typeDecls :: [TS.TSType]
diff --git a/test/Servant/Client/TypeScriptSpec.hs b/test/Servant/Client/TypeScriptSpec.hs
--- a/test/Servant/Client/TypeScriptSpec.hs
+++ b/test/Servant/Client/TypeScriptSpec.hs
@@ -1,5 +1,5 @@
 {-# LANGUAGE QuasiQuotes #-}
-{-# OPTIONS_GHC -Wno-unused-top-binds #-}
+{-# OPTIONS_GHC -Wno-incomplete-record-updates #-}
 
 module Servant.Client.TypeScriptSpec
   ( main
@@ -16,14 +16,16 @@
   )
 import qualified Data.Aeson.Generics.TypeScript as GGT (gen)
 import           Data.Char (isAscii, isDigit, isLetter, toLower)
+import qualified Data.Conduit.List as CL
 import           Data.Kind (Type)
 import           Data.List.Split (splitOn)
 import           Data.Proxy (Proxy (Proxy))
 import           Data.String (IsString)
 import           Data.String.Interpolate (i)
-import           Data.Time.Clock (getCurrentTime)
+import           Data.Time.Clock.POSIX (getPOSIXTime)
 import           GHC.Generics (Generic)
 import           Network.Wai.Handler.Warp (testWithApplication)
+import           Servant (serveDirectoryFileServer)
 import           Servant.API
   ( Capture
   , Description
@@ -35,14 +37,21 @@
   , QueryFlag
   , QueryParam
   , QueryParams
+  , Raw
   , ReqBody
   , Summary
+  , type (:<|>) (..)
   , type (:>)
   )
+import           Servant.API.WebSocketConduit (WebSocketConduit)
 import qualified Servant.Client.TypeScript as SCT (gen)
 import           Servant.Client.TypeScript (GenAll)
-import           Servant.Server (Application, serve)
-import           System.Directory (getTemporaryDirectory, removeFile)
+import           Servant.Server (HasServer, Server, serve)
+import           System.Directory
+  ( doesFileExist
+  , getTemporaryDirectory
+  , removeFile
+  )
 import           System.Exit (ExitCode (ExitFailure))
 import           System.FilePath ((<.>), (</>))
 import           System.Process (readProcessWithExitCode)
@@ -50,12 +59,25 @@
 import           Test.Hspec (Spec, describe, hspec, it, parallel, shouldBe)
 import           Test.QuickCheck (generate, resize, suchThat)
 import           Test.QuickCheck.Arbitrary (Arbitrary (arbitrary))
+import           Test.WebDriver
+  ( Browser (chromeOptions)
+  , asyncJS
+  , chrome
+  , closeSession
+  , defaultConfig
+  , openPage
+  , runSession
+  , useBrowser
+  )
 
+-- | TEST SETTINGS
 showLineNumbers :: Bool
 showLineNumbers = True
-
 parRuns :: Int
 parRuns = 5
+isHeadless :: Bool
+isHeadless = True
+--
 
 type User :: Type
 data User = User
@@ -129,6 +151,17 @@
   :> QueryParam "hu hu" String
   :> Get '[JSON] User
 
+type WebSocketAPI :: Type
+type WebSocketAPI
+  = "foo" :> "bar"
+  :> WebSocketConduit User User
+
+type WebSocketSecureAPI :: Type
+type WebSocketSecureAPI
+  = "foo" :> "bar"
+  :> Header "Dunno" String
+  :> WebSocketConduit User User
+
 main :: IO ()
 main = hspec spec
 
@@ -139,6 +172,42 @@
 
 printSpec :: Spec
 printSpec = do
+  it "Should be a trusting type casting WebSocket" $ SCT.gen @WebSocketAPI `shouldBe` [i|const API = {
+  base: "",
+  "/foo/bar": ():
+    Promise<{ send : (input: User) => void
+            , receive : (cb: (output: User) => void) => void
+            , raw : WebSocket
+    }> => {
+      const pr = window.location.protocol === "http:" ? "ws:" : "wss:";
+      const ws = new WebSocket(`${pr}//${window.location.host}${API.base}/foo/bar`);
+      return Promise.resolve({
+        send: (input: User) => ws.send(JSON.stringify(input)),
+        receive: (cb: ((output: User) => void)) =>
+          ws.onmessage = (message: MessageEvent<string>) => cb(JSON.parse(message.data)),
+        raw: ws
+      });
+  }
+};|]
+
+  it "Should allow for the WebSocket SEC header" $ SCT.gen @WebSocketSecureAPI `shouldBe` [i|const API = {
+  base: "",
+  "/foo/bar{Dunno}": (Dunno:string):
+    Promise<{ send : (input: User) => void
+            , receive : (cb: (output: User) => void) => void
+            , raw : WebSocket
+    }> => {
+      const pr = window.location.protocol === "http:" ? "ws:" : "wss:";
+      const ws = new WebSocket(`${pr}//${window.location.host}${API.base}/foo/bar`, [Dunno]);
+      return Promise.resolve({
+        send: (input: User) => ws.send(JSON.stringify(input)),
+        receive: (cb: ((output: User) => void)) =>
+          ws.onmessage = (message: MessageEvent<string>) => cb(JSON.parse(message.data)),
+        raw: ws
+      });
+  }
+};|]
+
   it "Should interpolate variables into the url for Capture" $
     let open = '/' : "*"; close = '*' : "/"
     in SCT.gen @CaptureAPI `shouldBe` [i|const API = {
@@ -146,7 +215,7 @@
   #{open}
    * This is the description of this route
    #{close}
-  "/foo/bar/:Frog%20Splat/:wat/Zap/:zazzy": (Frog_Splat:number,wat:string,zazzy:boolean): Promise<User> => {
+  "/foo/bar/:Frog%20Splat/:wat/Zap/:zazzy": async (Frog_Splat:number,wat:string,zazzy:boolean): Promise<User> => {
     const uri = `${API.base}/foo/bar/${Frog_Splat}/${wat}/Zap/${zazzy}`;
     return fetch(uri, {
       method: "GET"
@@ -157,7 +226,7 @@
   it "Should interpolate variables into the url for Query" $ SCT.gen @QueryAPI `shouldBe` [i|const API = {
   base: "",
   // This is the summary of this route
-  "/foo/bar/Zap?Frog%20Splat&wat&zazzy": (Frog_Splat:number,wat:boolean,zazzy:Array<string>): Promise<User> => {
+  "/foo/bar/Zap?Frog%20Splat&wat&zazzy": async (Frog_Splat:number,wat:boolean,zazzy:Array<string>): Promise<User> => {
     const uri = `${API.base}/foo/bar/Zap?Frog%20Splat=${Frog_Splat}&wat=${wat}&${zazzy.reduceRight((acc,x) => "zazzy=" + x + (acc ? "&" + acc : ""), "")}`;
     return fetch(uri, {
       method: "POST"
@@ -167,7 +236,7 @@
 
   it "Should interpolate variables into the url for Header" $ SCT.gen @HeaderAPI `shouldBe` [i|const API = {
   base: "",
-  "/foo/bar/Zap{Frog-Splat,wat,zazzy}": (Frog_Splat:number,wat:string,zazzy:boolean): Promise<User> => {
+  "/foo/bar/Zap{Frog-Splat,wat,zazzy}": async (Frog_Splat:number,wat:string,zazzy:boolean): Promise<User> => {
     const uri = `${API.base}/foo/bar/Zap`;
     return fetch(uri, {
       method: "POST",
@@ -180,9 +249,19 @@
   }
 };|]
 
+  it "Should interpolate variables into the url for Fragment" $ SCT.gen @FragmentAPI `shouldBe` [i|const API = {
+  base: "",
+  "/foo/bar\#fragment": async (): Promise<string> => {
+    const uri = `${API.base}/foo/bar`;
+    return fetch(uri, {
+      method: "POST"
+    }).then(res => res.json());
+  }
+};|]
+
   it "Should interpolate variables into the url for Request Body" $ SCT.gen @BodyAPI `shouldBe` [i|const API = {
   base: "",
-  "/foo/bar(User)": (User:User): Promise<User> => {
+  "/foo/bar(User)": async (User:User): Promise<User> => {
     const uri = `${API.base}/foo/bar`;
     return fetch(uri, {
       method: "POST",
@@ -196,7 +275,7 @@
 
   it "Should interpolate variables into the url for Mixed" $ SCT.gen @MixedCaptureQueryAPI `shouldBe` [i|const API = {
   base: "",
-  "/foo/bar/:Frog%20Splat/Zap/:zazzy?wat&rump&hu%20hu{bip,wip}": (Frog_Splat:number,wat:string,bip:string,wip:number,rump:boolean,zazzy:boolean,hu_hu:string): Promise<User> => {
+  "/foo/bar/:Frog%20Splat/Zap/:zazzy?wat&rump&hu%20hu{bip,wip}": async (Frog_Splat:number,wat:string,bip:string,wip:number,rump:boolean,zazzy:boolean,hu_hu:string): Promise<User> => {
     const uri = `${API.base}/foo/bar/${Frog_Splat}/Zap/${zazzy}?wat=${wat}&rump=${rump}&hu%20hu=${hu_hu}`;
     return fetch(uri, {
       method: "GET",
@@ -223,85 +302,139 @@
 
 roundTrips :: Spec
 roundTrips = do
+  pprop "Should round trip for WebSocket health" parRuns \((age,toJSBool -> isAdmin',encode -> names) :: (Int,Bool,[AlphaNumAscii])) ->
+   shouldRoundTrip @WebSocketAPI
+    (CL.map id)
+    ( "/foo/bar"
+    , ""
+    , [i|
+    const msg = { names: #{names}, age: #{age}, isAdmin: #{isAdmin'} };
+    res.receive(msg_ => {
+      if(JSON.stringify(msg_.names) === JSON.stringify(msg.names)
+         && msg_.age === msg.age
+         && msg_.isAdmin === msg.isAdmin){
+        return #{resolveSuccess}
+      }
+      return resolve('msg did not echo, got this instead: ' + JSON.stringify(msg_));
+    })
+    res.raw.onopen = () => res.send(msg);
+    |])
+
   pprop "Should round trip for Query" parRuns \((age,toJSBool -> isAdmin',encode -> names) :: (Int,Bool,[AlphaNumAscii])) ->
    shouldRoundTrip @QueryAPI
-    (serve (Proxy @QueryAPI) (\ns g a -> User (AlphaNumAscii <$> a) <$> echoMaybe ns <*> echoMaybe g))
+    (\ns g a -> User (AlphaNumAscii <$> a) <$> echoMaybe ns <*> echoMaybe g)
     ( "/foo/bar/Zap?Frog%20Splat&wat&zazzy"
     , [i|#{age},#{isAdmin'},#{names}|]
     , [i|
-    if(JSON.stringify(res.names) !== JSON.stringify(#{names}) || res.age !== #{age} || res.isAdmin !== #{isAdmin'}){
-      throw new Error('responded with ' + JSON.stringify(res) + "\\n" + JSON.stringify(#{names}))
+    if(JSON.stringify(res.names) !== JSON.stringify(#{names})
+       || res.age !== #{age}
+       || res.isAdmin !== #{isAdmin'}){
+      return resolve('responded with ' + JSON.stringify(res) + "\\n" + JSON.stringify(#{names}));
     }
-    return res|])
+    return #{resolveSuccess};|])
 
   pprop "Should round trip for Capture" parRuns \((age,name,toJSBool -> isAdmin') :: (Int,AlphaNumAscii,Bool)) ->
    let names' = [encode name] in
    shouldRoundTrip @CaptureAPI
-    (serve (Proxy @CaptureAPI) (\ns a g -> pure $ User (pure $ AlphaNumAscii a) ns g))
+    (\ns a g -> pure $ User (pure $ AlphaNumAscii a) ns g)
     ( "/foo/bar/:Frog%20Splat/:wat/Zap/:zazzy"
     , [i|#{age},`#{name}`,#{isAdmin'}|]
     , [i|
-    if(JSON.stringify(res.names) !== JSON.stringify(#{names'}) || res.age !== #{age} || res.isAdmin !== #{isAdmin'}){
-      throw new Error('responded with ' + JSON.stringify(res) + "\\n" + JSON.stringify(#{names'}))
+    if(JSON.stringify(res.names) !== JSON.stringify(#{names'})
+       || res.age !== #{age}
+       || res.isAdmin !== #{isAdmin'}){
+      return resolve('responded with ' + JSON.stringify(res) + "\\n" + JSON.stringify(#{names'}));
     }
-    return res|])
+    return #{resolveSuccess};|])
 
   pprop "Should round trip for Header" parRuns \((age,name,toJSBool -> isAdmin') :: (Int,AlphaNumAscii,Bool)) ->
    let names = encode [name] in shouldRoundTrip @HeaderAPI
-    (serve (Proxy @HeaderAPI) (\ns a g -> User <$> (pure . AlphaNumAscii <$> echoMaybe a) <*> echoMaybe ns <*> echoMaybe g))
+    (\ns a g -> User <$> (pure . AlphaNumAscii <$> echoMaybe a) <*> echoMaybe ns <*> echoMaybe g)
     ( "/foo/bar/Zap{Frog-Splat,wat,zazzy}"
     , [i|#{age},#{name},#{isAdmin'}|]
     , [i|
-    if(JSON.stringify(res.names) !== `#{names}` || res.age !== #{age} || res.isAdmin !== #{isAdmin'}){
-      throw new Error('should respond with ' + JSON.stringify(res))
+    if(JSON.stringify(res.names) !== `#{names}`
+      || res.age !== #{age}
+      || res.isAdmin !== #{isAdmin'}){
+      return resolve('should respond with ' + JSON.stringify(res));
     }
-    return res|])
+    return #{resolveSuccess};|])
 
+  pprop "Should round trip for Fragment" parRuns \(frag :: AlphaNumAscii) -> shouldRoundTrip @FragmentAPI
+    (pure $ unAlphaNumAscii frag)
+    ( "/foo/bar#fragment"
+    , mempty
+    , [i|
+    if(#{frag} !== res){
+      return resolve('should respond with ' + JSON.stringify(res));
+    }
+    return #{resolveSuccess};|])
+
   pprop "Should round trip for Request Body" parRuns \User{..} ->
     let isAdmin' = toJSBool isAdmin
         names' = encode names
     in shouldRoundTrip @BodyAPI
-    (serve (Proxy @BodyAPI) pure)
+    pure
     ( "/foo/bar(User)"
     , [i|{names:#{names},age:#{age},isAdmin:#{isAdmin'}}|]
     , [i|
-    if(JSON.stringify(res.names) !== `#{names'}` || res.age !== #{age} || res.isAdmin !== #{isAdmin'}){
-      throw new Error('should respond with true')
+    if(JSON.stringify(res.names) !== `#{names'}`
+       || res.age !== #{age}
+       || res.isAdmin !== #{isAdmin'}){
+      return resolve("User was not as expected");
     }
-    return res|])
+    return #{resolveSuccess};|])
 
   it "Should round trip for Mixed" $ shouldRoundTrip @MixedCaptureQueryAPI
-    (serve (Proxy @MixedCaptureQueryAPI) (\_ _ _ _ _ _ _ -> pure $ User ["jack"] 22 True))
+    (\_ _ _ _ _ _ _ -> pure $ User ["jack"] 22 True)
     ( "/foo/bar/:Frog%20Splat/Zap/:zazzy?wat&rump&hu%20hu{bip,wip}"
     , "3,'wazzy','zammy',4,false,true,'grim'"
     , [i|
-    if(res.names[0] === "jack" && res.age === 22 && res.isAdmin){
-      return res;
+    if(res.names[0] === "jack"
+       && res.age === 22
+       && res.isAdmin){
+      return #{resolveSuccess};
     }
-    throw new Error('should respond with true')|])
+    return resolve("User was not as expected");
+    |])
 
+srid :: String -> String
+srid script = [i|<html>
+<head>
+  <script src="/#{script <.> "js"}"></script>
+</head>
+<body></body>
+</html>|]
 
-shouldRoundTrip :: forall (api :: Type). GenAll api => Application -> (String, String, String) -> IO ()
-shouldRoundTrip app (path, args, test) = testWithApplication (pure app) $ \port -> do
-  tmpDir :: FilePath <- getTemporaryDirectory
+resolveSuccess :: String
+resolveSuccess = [i|resolve("success")|]
+
+shouldRoundTrip :: forall (api :: Type).
+  ( HasServer api '[]
+  , GenAll api
+  ) => Server api -> (String, String, String) -> IO ()
+shouldRoundTrip app (path, args, test)
+ = getTemporaryDirectory >>= \tmpDir ->
+ testWithApplication (pure $ serve (Proxy @(api :<|> Raw)) (app :<|> serveDirectoryFileServer tmpDir)) $ \port -> do
   rand :: Int <- randomIO
-  now <- getCurrentTime
+  now <- getPOSIXTime
 
   let
-    base, filePath, fetchAPI, typeDecl, ts :: String
-    base = "http://127.0.0.1:" <> show port
-    filePath = tmpDir </> show now <> show (rand * 1000000) <.> "ts"
-    fetchAPI = SCT.gen @api
-    typeDecl = printTS $ GGT.gen @User
-    ts = [i|#{typeDecl}
-#{fetchAPI}
-API.base = "#{base}";
-API["#{path}"](#{args}).then(res => {
-  #{test}
-});|] :: String
+    tsFilePath, localhost, ts :: String
+    localhost = "http://127.0.0.1:" <> show port
+    tsFilePath = show now <> show (rand * 1000000) <.> "ts"
+    ts = [i|#{printTS $ GGT.gen @User}
+#{SCT.gen @api}
+window["test"] = resolve => {
+  API["#{path}"](#{args}).then(res => {
+    #{test}
+  });
+};|]
+
     death = do
-      removeFile filePath
-      removeFile $ filePath <.> "js"
+      removeFileIfExists $ tmpDir </> tsFilePath
+      removeFileIfExists $ tmpDir </> tsFilePath <.> "js"
+      removeFileIfExists $ tmpDir </> tsFilePath <.> "html"
 
     handleFailure :: forall a. (ExitCode, String, String) -> String -> IO a -> IO a
     handleFailure res mes continue = case res of
@@ -316,14 +449,39 @@
         error $ mes <> " " <> show ef
       _ -> continue
 
-  writeFile filePath ts
-  tscres <- readProcessWithExitCode "tsc" [filePath, "--outFile", filePath <.> "js" ] ""
+  writeFile (tmpDir </> tsFilePath) ts
+  writeFile (tmpDir </> tsFilePath <.> "html") (srid tsFilePath)
+
+  tscres <- readProcessWithExitCode "tsc"
+    [ tmpDir </> tsFilePath
+    , "--lib", "ES2021,DOM"
+    , "--module", "system"
+    , "--outFile", tmpDir </> tsFilePath <.> "js"
+    ] ""
   handleFailure tscres "TSC exited with" do
-    nodeRes <- readProcessWithExitCode "node" [filePath <.> "js"] ""
-    handleFailure nodeRes "Node exited with" death
+    res <- runSession
+      (useBrowser chrome { chromeOptions = if isHeadless then
+        [ "--headless"
+        , "--disable-gpu"
+        ] else [] } defaultConfig)
+      do
+        openPage $ localhost </> tsFilePath <.> "html"
+        res <- asyncJS [] [i|test(arguments[0])|]
+        closeSession
+        return res
+    case res of
+      Just "success" -> pure ()
+      Just x         -> death >> error x
+      Nothing        -> death >> error "TIMEOUT"
 
+removeFileIfExists :: FilePath -> IO ()
+removeFileIfExists fp = do
+  exists <- doesFileExist fp
+  when exists $ removeFile fp
+
 addLineNumbers :: String -> String
 addLineNumbers ts =
   if showLineNumbers then foldMap (\(x,ln) ->
-    let lnf = show (ln :: Int) in "\n " <> lnf <> replicate (4 - length lnf) ' ' <> "| " <> x) $ zip (splitOn "\n" ts) [1..]
+    let lnf = show (ln :: Int) in "\n " <> lnf <> replicate (4 - length lnf) ' ' <> "| " <> x)
+      $ zip (splitOn "\n" ts) [1..]
   else ts
