diff --git a/CHANGELOG.md b/CHANGELOG.md
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -1,3 +1,16 @@
+0.2.3
+=====
+
+*   Added a test-suite to the package that uses PhantomJS to simulate a
+    browser client.
+
+*   Pass on websocket requests unchanged to the application. Add documentation
+    that reminds the application author to check the `Origin` header for
+    websocket requests.
+
+*   Move development source repository from https://github.com/alephcloud/wai-cors
+    to https://github.com/larskuhtz/wai-cors.
+
 0.2.2
 =====
 
diff --git a/README.md b/README.md
--- a/README.md
+++ b/README.md
@@ -1,21 +1,53 @@
-[![Build Status](https://travis-ci.org/alephcloud/wai-cors.svg?branch=master)](https://travis-ci.org/alephcloud/wai-cors)
+[![Build Status](https://travis-ci.org/larskuhtz/wai-cors.svg?branch=master)](https://travis-ci.org/larskuhtz/wai-cors)
 
 Cross-Origin Resource Sharing (CORS) For Wai
 ============================================
 
-This package provides a Haskell implemenation of CORS for
+This package provides a Haskell implementation of CORS for
 [WAI](http://hackage.haskell.org/package/wai)
 that aims to be compliant with
 [http://www.w3.org/TR/cors](http://www.w3.org/TR/cors).
 
+Note On Security
+----------------
+
+This implementation doesn't include any server side enforcement. By complying
+with the CORS standard it enables the client (i.e. the web browser) to enforce
+the CORS policy. For application authors it is strongly recommended to take
+into account the security considerations in section 6.3 of the
+[CORS standard](http://wwww.w3.org/TR/cors). In particular the application should
+check that the value of the `Origin` header matches the expectations.
+
+Websocket connections don't support CORS and are ignored by the CORS implementation
+in this package. However Websocket requests usually (at least for some
+browsers) include the @Origin@ header. Applications are expected to check the
+value of this header and respond with an error in case that its content doesn't
+match the expectations.
+
+Installation
+------------
+
+Assuming the availability of recent versions of
+[GHC](https://www.haskell.org/ghc/) and [cabal](https://www.haskell.org/cabal/)
+this package is installed via
+
+```.bash
+cabal update
+cabal install wai-cors
+```
+
 Usage
 -----
 
-The file `test/server.hs` shows how to support simple cross-origin requests (as
+The function 'simpleCors' enables support of simple cross-origin requests. More
+advanced CORS policies can be enabled by passing a 'CorsResourcePolicy' to the
+'cors' middleware.
+
+The file `examples/Scotty.hs` shows how to support simple cross-origin requests (as
 defined in [http://www.w3.org/TR/cors](http://www.w3.org/TR/cors)) in a
 [scotty](http://hackage.haskell.org/package/scotty) application.
 
-~~~{.haskell}
+```.haskell
 {-# LANGUAGE UnicodeSyntax #-}
 {-# LANGUAGE OverloadedStrings #-}
 
@@ -30,30 +62,40 @@
 main = scotty 8080 $ do
     middleware simpleCors
     matchAny  "/" $ text "Success"
-~~~
+```
 
-The result of following curl command will include the HTTP response 
+The result of following curl command will include the HTTP response
 header `Access-Control-Allow-Origin: *`.
 
-~~~{.bash}
+```.bash
 curl -i http://127.0.0.1:8888 -H 'Origin: 127.0.0.1' -v
-~~~
+```
 
 Documentation for more general usage can be found in the module
 [Network.Wai.Middleware.Cors](http://hackage.haskell.org/package/wai-cors/docs/Network-Wai-Middleware-Cors.html).
 
-TEST
+Test
 ----
 
-Currently there is only basic support to test simple cross-origin
-request from a browser.
+In order to run the automated test suite [PhantomJS](http://phantomjs.org/) (at
+least version 2.0) must be installed in the system.
 
-Start server:
+```.bash
+cabal install --only-dependencies --enable-tests
+cabal test --show-details=streaming
+```
 
-~~~{.bash}
+If [PhantomJS](http://phantomjs.org/) is not available the tests can be
+exectued manually in a modern web-browser as follows.
+
+Start the server application:
+
+```.bash
 cd test
-runHaskell server.hs
-~~~
+ghc -main-is Server Server.hs
+./Server
+```
 
-Open the file `test/index.html` in a modern web-browser in order to run some
-simple tests.
+Open the file `test/index.html` in a modern web-browser. On page load a Javascript
+script is exectued that runs the test suite and prints the result on the page.
+
diff --git a/constraints b/constraints
--- a/constraints
+++ b/constraints
@@ -1,11 +1,12 @@
 constraints: array ==0.5.1.0,
-             attoparsec ==0.12.1.5,
+             attoparsec ==0.13.0.0,
              base ==4.8.0.0,
              base-unicode-symbols ==0.2.2.4,
+             binary ==0.7.3.0,
              blaze-builder ==0.4.0.1,
              bytestring ==0.10.6.0,
              case-insensitive ==1.2.0.4,
-             charset ==0.3.7,
+             charset ==0.3.7.1,
              containers ==0.5.6.2,
              deepseq ==1.4.1.1,
              ghc-prim ==0.4.0.0,
@@ -14,13 +15,13 @@
              integer-gmp ==1.0.0.0,
              mtl ==2.2.1,
              nats ==1,
-             network ==2.6.0.2,
+             network ==2.6.1.0,
              parsec ==3.1.9,
-             parsers ==0.12.1.1,
+             parsers ==0.12.2.1,
              rts ==1.0,
              scientific ==0.3.3.8,
              semigroups ==0.16.2.2,
-             text ==1.2.0.4,
+             text ==1.2.1.0,
              time ==1.5.0.1,
              transformers ==0.4.2.0,
              unix ==2.7.1.0,
diff --git a/examples/Scotty.hs b/examples/Scotty.hs
new file mode 100644
--- /dev/null
+++ b/examples/Scotty.hs
@@ -0,0 +1,23 @@
+{-# LANGUAGE UnicodeSyntax #-}
+{-# LANGUAGE OverloadedStrings #-}
+
+-- |
+-- Module: Main
+-- Description: Exampe for using wai-cors with scotty
+-- Copyright: © 2015 Lars Kuhtz <lakuhtz@gmail.com>
+-- License: MIT
+-- Maintainer: Lars Kuhtz <lakuhtz@gmail.com>
+-- Stability: experimental
+--
+module Main
+( main
+) where
+
+import Network.Wai.Middleware.Cors
+import Web.Scotty
+
+main ∷ IO ()
+main = scotty 8080 $ do
+    middleware simpleCors
+    matchAny  "/" $ text "Success"
+
diff --git a/src/Network/Wai/Middleware/Cors.hs b/src/Network/Wai/Middleware/Cors.hs
--- a/src/Network/Wai/Middleware/Cors.hs
+++ b/src/Network/Wai/Middleware/Cors.hs
@@ -1,4 +1,5 @@
 -- ------------------------------------------------------ --
+-- Copyright © 2015 Lars Kuhtz <lakuhtz@gmail.com>
 -- Copyright © 2014 AlephCloud Systems, Inc.
 -- ------------------------------------------------------ --
 
@@ -14,8 +15,29 @@
 -- | An implemenation of Cross-Origin resource sharing (CORS) for WAI that
 -- aims to be compliant with <http://www.w3.org/TR/cors>.
 --
--- The function 'simpleCors' enables support of simple cross-origin requests.
+-- The function 'simpleCors' enables support of simple cross-origin requests. More
+-- advanced CORS policies can be enabled by passing a 'CorsResourcePolicy' to the
+-- 'cors' middleware.
 --
+-- = Note On Security
+--
+-- This implementation doens't include any server side enforcement. By
+-- complying with the CORS standard it enables the client (i.e. the web
+-- browser) to enforce the CORS policy. For application authors it is strongly
+-- recommended to take into account the security considerations in section 6.3
+-- of <http://wwww.w3.org/TR/cors>. In particular the application should check
+-- that the value of the @Origin@ header matches it's expectations.
+--
+-- = Websockets
+--
+-- Websocket connections don't support CORS and are ignored by this CORS
+-- implementation. However Websocket requests usually (at least for some
+-- browsers) include the @Origin@ header. Applications are expected to check
+-- the value of this header and respond with an error in case that its content
+-- doesn't match the expectations.
+--
+-- = Example
+--
 -- The following is an example how to enable support for simple cross-origin requests
 -- for a <http://hackage.haskell.org/package/scotty scotty> application.
 --
@@ -58,6 +80,10 @@
 #define MIN_VESION_base(x,y,z) 1
 #endif
 
+#ifndef MIN_VESION_wai
+#define MIN_VESION_wai(x,y,z) 1
+#endif
+
 #if ! MIN_VERSION_base(4,8,0)
 import Control.Applicative
 #endif
@@ -147,7 +173,7 @@
     -- If this is 'False' and the request does not include an @Origin@ header
     -- the request is passed on unchanged to the application.
     --
-    -- /since version 0.2/
+    -- @since 0.2
     , corsRequireOrigin ∷ !Bool
 
     -- | In the case that
@@ -165,7 +191,7 @@
     -- * an response with HTTP status 400 (bad request) and short
     --   error message is returned if this field is 'False'.
     --
-    -- /since version 0.2/
+    -- @since 0.2
     --
     , corsIgnoreFailures ∷ !Bool
     }
@@ -273,6 +299,9 @@
 #else
 cors policyPattern app r
 #endif
+    -- We don't handle websockets, even if they include an @Origin@ header
+    | isWebSocketsReq r = runApp
+
     | Just policy ← policyPattern r = case hdrOrigin of
 
         -- No origin header: requect request
@@ -519,4 +548,12 @@
     ∷ B8.ByteString -- ^ body
     → WAI.Response
 corsFailure msg = WAI.responseLBS HTTP.status400 [("Content-Type", "text/html; charset-utf-8")] (LB8.fromStrict msg)
+
+-- Copied from the [wai-websocket package](https://github.com/yesodweb/wai/blob/master/wai-websockets/Network/Wai/Handler/WebSockets.hs#L21)
+--
+isWebSocketsReq
+    ∷ WAI.Request
+    → Bool
+isWebSocketsReq req =
+    fmap CI.mk (lookup "upgrade" $ WAI.requestHeaders req) == Just "websocket"
 
diff --git a/test/PhantomJS.hs b/test/PhantomJS.hs
new file mode 100644
--- /dev/null
+++ b/test/PhantomJS.hs
@@ -0,0 +1,70 @@
+{-# LANGUAGE LambdaCase #-}
+{-# LANGUAGE OverloadedStrings #-}
+{-# LANGUAGE ScopedTypeVariables #-}
+{-# LANGUAGE UnicodeSyntax #-}
+
+-- |
+-- Module: Main
+-- Description: Test suite that uses PhantomJS to simulate a browser
+-- Copyright: © 2015 Lars Kuhtz <lakuhtz@gmail.com>
+-- License: MIT
+-- Maintainer: Lars Kuhtz <lakuhtz@gmail.com>
+-- Stability: experimental
+--
+module Main
+( main
+) where
+
+import Control.Concurrent
+
+import Data.Monoid.Unicode
+import Data.String
+
+import System.Directory
+import System.FilePath
+import System.Exit
+import System.IO
+import System.Process
+
+-- internal modules
+
+import qualified Server as Server
+
+phantomJsBinaryPath ∷ FilePath
+phantomJsBinaryPath = "phantomjs"
+
+phantomJsArgs ∷ IsString a ⇒ [a]
+phantomJsArgs = ["--ignore-ssl-errors=true"]
+--phantomJsArgs = ["--ignore-ssl-errors=true", "--debug=true"]
+
+phantomJsScriptPath ∷ FilePath
+phantomJsScriptPath = "test/phantomjs.js"
+
+indexFilePath ∷ FilePath
+indexFilePath = "test/index.html"
+
+runPhantomJs ∷ IO ()
+runPhantomJs = do
+
+    -- check that phantomJS binary is available
+    -- FIXME check the version
+    findExecutable phantomJsBinaryPath >>= \case
+        Nothing → do
+            hPutStrLn stderr $ "Missing PhantomJS exectuable: in order to run this test-suite PhantomJS must be availabe on the system"
+            exitFailure
+        Just _ → return ()
+
+    pwd ← getCurrentDirectory
+    -- FIXME I consider it an API bug of the directory package that in order
+    -- to get the exit code we have also capture stdout and stderr.
+    (code, out, err) ← readProcessWithExitCode phantomJsBinaryPath (args pwd) ""
+    hPutStrLn stdout out
+    hPutStrLn stderr err
+    exitWith code
+  where
+    args pwd = phantomJsArgs ⊕ [phantomJsScriptPath] ⊕ [pwd </> indexFilePath]
+
+main ∷ IO ()
+main = do
+    _ ← forkIO $ Server.main
+    runPhantomJs
diff --git a/test/Server.hs b/test/Server.hs
new file mode 100644
--- /dev/null
+++ b/test/Server.hs
@@ -0,0 +1,63 @@
+{-# LANGUAGE CPP #-}
+{-# LANGUAGE LambdaCase #-}
+{-# LANGUAGE OverloadedStrings #-}
+{-# LANGUAGE ScopedTypeVariables #-}
+{-# LANGUAGE UnicodeSyntax #-}
+
+-- |
+-- Module: Server
+-- Description: Test HTTP server for wai-cors
+-- Copyright: © 2015 Lars Kuhtz <lakuhtz@gmail.com>.
+-- License: MIT
+-- Maintainer: Lars Kuhtz <lakuhtz@gmail.com>
+-- Stability: experimental
+--
+module Server
+( main
+) where
+
+#ifndef MIN_VERSION_wai
+#define MIN_VERSION_wai(a,b,c) 1
+#endif
+
+import Control.Concurrent
+import Control.Exception
+import Control.Monad
+
+import Network.Socket (withSocketsDo)
+import Network.Wai.Middleware.Cors
+import qualified Network.HTTP.Types as HTTP
+import qualified Network.Wai as WAI
+import qualified Network.Wai.Handler.Warp as WARP
+import qualified Network.Wai.Handler.WebSockets as WS
+import qualified Network.WebSockets as WS
+
+import qualified Data.Text as T
+
+main ∷ IO ()
+main = withSocketsDo $
+    WARP.run 8080 $ simpleCors waiapp
+
+waiapp ∷ WAI.Application
+waiapp = WS.websocketsOr WS.defaultConnectionOptions wsserver $ \_ → app
+  where
+#if MIN_VERSION_wai(2,0,0)
+    app respond = respond $ WAI.responseLBS HTTP.status200 [] "Success"
+#else
+    app = WAI.responseLBS HTTP.status200 [] "Success"
+#endif
+
+-- -------------------------------------------------------------------------- --
+-- Websockets Server
+
+wsserver ∷ WS.ServerApp
+wsserver pc = do
+    c ← WS.acceptRequest pc
+    forever (go c) `catch` \case
+        WS.CloseRequest _code _msg → WS.sendClose c ("closed" ∷ T.Text)
+        e → throwIO e
+  where
+    go c = do
+        msg ← WS.receiveDataMessage c
+        forkIO $ WS.sendDataMessage c msg
+
diff --git a/test/index.html b/test/index.html
--- a/test/index.html
+++ b/test/index.html
@@ -1,64 +1,283 @@
 <!DOCTYPE html>
+<!-- Copyright (c) 2015 Lars Kuhtz <lakuhtz@gmail.coml> -->
 <html>
     <head> 
     <script type="text/javascript" src="https://ajax.googleapis.com/ajax/libs/jquery/2.1.0/jquery.min.js"></script>
     <script type="text/javascript">
 
     var url = 'http://localhost:8080'
-    var simpleTest = function (label, result, method, creds, contentType, headers) {
+    var wsurl = 'ws://localhost:8080'
 
-        var xhr = new XMLHttpRequest();
-        xhr.open(method, url, true);
+/* ************************************************************************** */
+/* Utils */
 
-        var succ = function() {
-            $('#results').append('<p style="color:green">' + label + ': success</p>')
+    /* asynchronous array map function
+     *
+     * The function f is called on each element in the value and
+     * is expected to return a promise object.
+     *
+     * asyncMap itself returns a promise object that is completed
+     * when the promises for all elements completed. When all element
+     * promises resolve the array promises is resolved. If at least
+     * one element promise is reject the array promises is rejected.
+     *
+     * The done and fail callbacks are called with an object of the form
+     *
+     * { 'successes':successes, 'failures': failures, 'results': results }
+     *
+     * The results that array has one value for each element of the original
+     * array. The values in the result array are objects of the form
+     *
+     * { success: trueOrFalse, result: elementPromiseResult }
+     *
+     * The progress callback is given an object of the form
+     *
+     * { successes: sumS, failures: numF, missing: numberOfMissingElementPromises }
+     */
+    var asyncMap = function (a,f) {
+        var def = $.Deferred();
+        var successes = 0;
+        var failures = 0;
+        var results = new Array(a.length);
+        var c = a.length;
+
+        a.map(function (val, idx, arr) {
+            t = f(val);
+            t.done(function(arg) {
+                successes++;
+                results[idx] = { 'success':true, 'result':arg };
+            });
+            t.fail(function(arg) {
+                failures++;
+                results[idx] = { 'success':false, 'result':arg };
+            });
+            t.always(function() {
+                c--;
+                if (c > 0) {
+                    def.notify({ 'successes':successes, 'failures': failures, 'missing': c });
+                } else {
+                    var result = { 'successes':successes, 'failures': failures, 'results': results };
+                    if (failures === 0) {
+                        def.resolve(result);
+                    } else {
+                        def.reject(result);
+                    };
+                };
+            });
+        });
+        return def.promise();
+    };
+
+    // monoid concatenation for asyncMap results
+    var concatResults = function (r1, r2) {
+        return {
+            'successes': r1.successes + r2.successes,
+            'failures': r1.failures + r2.failures,
+            'results': r1.results.concat(r2.results)
         };
+    }
 
-        var fail = function() {
-            $('#results').append('<p style="color:red">' + label + ': failure</p>')
+    // monoid identity for asyncMap results
+    var emptyResult = { 'successes': 0, 'failures': 0, 'results':[] };
+
+    // monad join for asyncMap results
+    var flattenResults = function (r) {
+        return r.results.reduce(function (p,c,i,a) { return concatResults(p,c.result); }, emptyResult);
+    };
+
+/* ************************************************************************** */
+/* PhantomJS Integration */
+
+    // Integration with PhantomJS
+    //
+    // call with: phantomjs --ignore-ssl-errors=true phantomjs.js $PWD/index.html
+    //
+    if (typeof window.callPhantom === 'function') {
+        window.setTimeout(function () { window.callPhantom("timeout"); },3000);
+    };
+
+    var phantomJsCb = function (result) { 
+        if (typeof window.callPhantom === 'function') {
+            window.callPhantom(result);
         };
+    };
 
-        xhr.onerror = result ? fail : succ;
-        xhr.onload = result ? succ : fail;
+/* ************************************************************************** */
+/* Run Tests */
 
-        xhr.setRequestHeader('content-type', contentType);
+    var runTest = function (params) {
 
-        for (var i = 0; i < headers.length; ++i) {
-            var e = headers[i];
-            xhr.setRequestHeader(e[1], e[2]);
+        var test = $.Deferred();
+        var req = $.ajax(
+            { 'method': params.method
+            , 'url': url
+            , 'contentType': params.contentType
+            , 'crossDomain': true
+            , 'headers': params.headers
+            });
+
+        var succ = function () { test.resolve(params); }
+        var fail = function () { test.reject(params); }
+        req.done(params.expectedResult ? succ : fail);
+        req.fail(params.expectedResult ? fail : succ);
+
+        return test.promise();
+    }
+
+    var pageTest = function (params) {
+        test = runTest(params);
+
+        test.done(function() {
+            $('#results').append('<p style="color:green">' + params.label + ': success</p>');
+        });
+
+        test.fail(function() {
+            $('#results').append('<p style="color:red">' + params.label + ': failure</p>');
+        });
+        return test;
+    }
+
+    var runAllPageTests = function (tests) {
+        return asyncMap(tests, pageTest);
+    }
+
+/* ************************************************************************** */
+/* WebSockets */
+
+    /* a super simple ws service client
+     *
+     * TODO add proper callbacks (e.g. close, error);
+     * TODO in case we use this for anything more complex we should add proper
+     *      streaming by supporting chunking.
+     *
+     */
+    var WsService = function (url, timeout) {
+        var timeout = timeout ? timeout : 1000;
+        var curMsgId = 0;
+        var ready = $.Deferred();
+        var websocket = new WebSocket(url);
+        var newMsgId = function () { return ++curMsgId; };
+
+        // maps message ids data to promise objects
+        var msgMap = {}
+
+        var ws = function (msg) {
+            var p = $.Deferred();
+            var req = { id: newMsgId(), msg: msg};
+            ready.then(function () { websocket.send(JSON.stringify(req)); });
+            msgMap[req.id] = p;
+            setTimeout(function () { p.reject("timeout: no response from websocket after " + timeout + " milliseconds"); }, timeout);
+            return p.promise();
+        };
+
+        websocket.onmessage = function (evt) {
+            var resp = JSON.parse(evt.data);
+            var prom = msgMap[resp.id];
+            if (prom) {
+                prom.resolve(resp.msg);
+            } else {
+                console.log("unexpected message from websocket: " + JSON.stringify(resp));
+            };
+        };
+
+        var wserror = function (evt) {
+            for (i in msgMap) {
+                if (msgMap.hasOwnProperty(i)) {
+                    msgMap[i].fail();
+                };
+            };
+            msgMap = {};
+        };
+
+        websocket.onopen = function(evt) {
+            ready.resolve();
+        };
+
+        websocket.onerror = function(evt) {
+            console.log("websocket error: " + evt.data);
+            wserror(evt);
+        };
+
+        websocket.onclose = function(evt) {
+            console.log("websocket closed unexpectedly: " + evt.data);
+            wserror(evt);
+        };
+
+        return ws;
+    };
+
+    var websocketTest = function () {
+
+        var ws = WsService(wsurl);
+
+        var wstest = function (msg) {
+            var p = ws(msg);
+
+            p.done(function() {
+                $('#wsresults').append('<p style="color:green">' + msg + ': success</p>');
+            });
+
+            p.fail(function() {
+                $('#wsresults').append('<p style="color:red">' + msg + ': failure</p>');
+            });
+            return p;
         }
-        xhr.send();
+
+        return asyncMap(["msg 0", "msg 1", "msg 2"], wstest);
+    };
+
+/* ************************************************************************** */
+/* Test Vectors */
+
+    var mkTest = function (label, expectedResult, method, credentials, contentType, headers) {
+        return {
+            label: label,
+            expectedResult: expectedResult,
+            method: method,
+            credentials: credentials,
+            contentType: contentType,
+            headers: headers
+        };
     }
 
-    $(document).ready(function() {
-        simpleTest("simple GET 1", true, "GET", false, 'text/plain', []);
-        simpleTest("simple GET 2", true, "GET", false, 'application/x-www-form-urlencoded', []);
-        simpleTest("simple GET 3", true, "GET", false, 'multipart/form-data', []);
-        simpleTest("simple GET 4", false, "GET", false, 'application/json', []);
-        simpleTest("simple GET 5", false, "GET", false, 'text/plain', [['abc','abc']]);
+    var tests = [
+        mkTest("simple GET 1", true, "GET", false, 'text/plain', []),
+        mkTest("simple GET 2", true, "GET", false, 'application/x-www-form-urlencoded', []),
+        mkTest("simple GET 3", true, "GET", false, 'multipart/form-data', []),
+        mkTest("simple GET 4", false, "GET", false, 'application/json', []),
+        mkTest("simple GET 5", false, "GET", false, 'text/plain', {'abc': 'abc'}),
 
-        simpleTest("simple HEAD 1", true, "HEAD", false, 'text/plain', []);
-        simpleTest("simple HEAD 2", true, "HEAD", false, 'application/x-www-form-urlencoded', []);
-        simpleTest("simple HEAD 3", true, "HEAD", false, 'multipart/form-data', []);
-        simpleTest("simple HEAD 4", false, "HEAD", false, 'application/json', []);
-        simpleTest("simple HEAD 5", false, "HEAD", false, 'text/plain', [['abc','abc']]);
+        mkTest("simple HEAD 1", true, "HEAD", false, 'text/plain', []),
+        mkTest("simple HEAD 2", true, "HEAD", false, 'application/x-www-form-urlencoded', []),
+        mkTest("simple HEAD 3", true, "HEAD", false, 'multipart/form-data', []),
+        mkTest("simple HEAD 4", false, "HEAD", false, 'application/json', []),
+        mkTest("simple HEAD 5", false, "HEAD", false, 'text/plain', {'abc': 'abc'}),
 
-        simpleTest("simple POST 1", true, "POST", false, 'text/plain', []);
-        simpleTest("simple POST 2", true, "POST", false, 'application/x-www-form-urlencoded', []);
-        simpleTest("simple POST 3", true, "POST", false, 'multipart/form-data', []);
-        simpleTest("simple POST 4", false, "POST", false, 'application/json', []);
-        simpleTest("simple POST 5", false, "POST", false, 'test/plain', [['abc','abc']]);
+        mkTest("simple POST 1", true, "POST", false, 'text/plain', []),
+        mkTest("simple POST 2", true, "POST", false, 'application/x-www-form-urlencoded', []),
+        mkTest("simple POST 3", true, "POST", false, 'multipart/form-data', []),
+        mkTest("simple POST 4", false, "POST", false, 'application/json', []),
+        mkTest("simple POST 5", false, "POST", false, 'test/plain', {'abc':'abc'}),
 
-        simpleTest("simple PUT 1", false, "PUT", false, 'text/plain', []);
-        simpleTest("simple DELETE 1", false, "DELETE", false, 'text/plain', []);
-        simpleTest("simple PATCH 1", false, "PATCH", false, 'text/plain', []);
+        mkTest("simple PUT 1", false, "PUT", false, 'text/plain', []),
+        mkTest("simple DELETE 1", false, "DELETE", false, 'text/plain', []),
+        mkTest("simple PATCH 1", false, "PATCH", false, 'text/plain', []),
+    ];
+
+    $(document).ready(function() {
+        var p = runAllPageTests(tests);
+        var q = websocketTest();
+        var r = asyncMap([p,q], function (x) { return x; });
+        r.always(function (x) { phantomJsCb(flattenResults(x)); });
     });
 
     </script>
     </head>
     <body>
         <h1>Results</h1>
-        <div id="results"/>
+        <div id="results"></div>
+        <h1>Websocket Test Results</h1>
+        <div id="wsresults"></div>
     </body>
 </html>
 
diff --git a/test/phantomjs.js b/test/phantomjs.js
new file mode 100644
--- /dev/null
+++ b/test/phantomjs.js
@@ -0,0 +1,39 @@
+/*
+ * Copyright (c) 2015 Lars Kuhtz <lakuhtz@gmail.com>
+ */
+
+var page = require('webpage').create();
+var system = require('system');
+var index = null;
+
+if (system.args.length === 1) {
+    index = 'https://rawgit.com/alephcloud/wai-cors/master/test/index.html'
+} else if (system.args.length === 2) {
+    index = 'file://' + system.args[1];
+} else {
+    console.log('Usage:');
+    console.log('   phantomjs phanomjs.js <absolute-index-file-path>');
+    console.log('   phantomjs phanomjs.js');
+    phantom.exit(3);
+}
+
+page.open(index, function(status) {
+  console.log("Status: " + status);
+  if(status === "success") {
+        console.log("test page loaded successfully");
+  } else {
+      console.log("failed to load test page");
+      phantom.exit(2);
+  }
+});
+
+page.onCallback = function(data) {
+    if (data.failures > 0) {
+        console.log("Some tests failed");
+        console.log( page.plainText );
+        phantom.exit(1);
+    } else {
+        console.log( page.plainText );
+        phantom.exit(0);
+    }
+}
diff --git a/test/server.hs b/test/server.hs
deleted file mode 100644
--- a/test/server.hs
+++ /dev/null
@@ -1,19 +0,0 @@
--- ------------------------------------------------------ --
--- Copyright © 2014 AlephCloud Systems, Inc.
--- ------------------------------------------------------ --
-
-{-# LANGUAGE UnicodeSyntax #-}
-{-# LANGUAGE OverloadedStrings #-}
-
-module Main
-( main
-) where
-
-import Network.Wai.Middleware.Cors
-import Web.Scotty
-
-main ∷ IO ()
-main = scotty 8080 $ do
-    middleware simpleCors
-    matchAny  "/" $ text "Success"
-
diff --git a/wai-cors.cabal b/wai-cors.cabal
--- a/wai-cors.cabal
+++ b/wai-cors.cabal
@@ -1,9 +1,10 @@
 -- ------------------------------------------------------ --
+-- Copyright © 2015 Lars Kuhtz <lakuhtz@gmail.com>
 -- Copyright © 2014 AlephCloud Systems, Inc.
 -- ------------------------------------------------------ --
 
 Name: wai-cors
-Version: 0.2.2
+Version: 0.2.3
 Synopsis: CORS for WAI
 
 Description:
@@ -12,13 +13,13 @@
     <http://hackage.haskell.org/package/wai Wai>
     that aims to be compliant with <http://www.w3.org/TR/cors>.
 
-Homepage: https://github.com/alephcloud/wai-cors
-Bug-reports: https://github.com/alephcloud/wai-cors/issues
+Homepage: https://github.com/larskuhtz/wai-cors
+Bug-reports: https://github.com/larskuhtz/wai-cors/issues
 License: MIT
 License-file: LICENSE
-Author: Lars Kuhtz <lars@alephcloud.com>
-Maintainer: Lars Kuhtz <lars@alephcloud.com>
-Copyright: Copyright (c) 2014 AlephCloud Systems, Inc.
+Author: Lars Kuhtz <lakuhtz@gmail.com>
+Maintainer: Lars Kuhtz <lakuhtz@gmail.com>
+Copyright: (c) 2015 Lars Kuhtz <lakuhtz@gmail.com>, (c) 2014 AlephCloud Systems, Inc.
 Category: Web
 Build-type: Simple
 Cabal-version: >= 1.14.0
@@ -28,16 +29,18 @@
     CHANGELOG.md
     constraints
     test/index.html
-    test/server.hs
+    test/phantomjs.js
+    examples/Scotty.hs
 
 source-repository head
     type: git
-    location: https://github.com/alephcloud/wai-cors
+    location: https://github.com/larskuhtz/wai-cors
+    branch: master
 
 source-repository this
     type: git
-    location: https://github.com/alephcloud/wai-cors
-    tag: 0.2.2
+    location: https://github.com/larskuhtz/wai-cors
+    tag: 0.2.3
 
 flag transformers-3
     description: use transformers<0.3 (this is set automatically)
@@ -49,6 +52,11 @@
     default: False
     manual: False
 
+flag wai-2
+    description: use version wai<3 (this is set automatically)
+    default: False
+    manual: False
+
 Library
     default-language: Haskell2010
     hs-source-dirs: src
@@ -69,10 +77,14 @@
     if flag (wai-1)
         build-depends:
             wai < 2.0,
-            resourcet >= 0.4
-    else
+            resourcet >= 0.4,
+            transformers < 0.4
+    if flag (wai-2)
         build-depends:
-            wai >= 2.0
+            wai < 3.0 && >= 2.0
+    if ! flag (wai-1) && ! flag (wai-2)
+        build-depends:
+            wai >= 3.0
 
     if flag(transformers-3)
         build-depends:
@@ -82,5 +94,41 @@
     else
         build-depends:
             mtl >= 2.2,
-            transformers >= 0.4
+            transformers >= 0.4,
+            wai >= 2
+
+    ghc-options: -Wall
+
+test-suite phantomjs
+    type: exitcode-stdio-1.0
+    default-language: Haskell2010
+    main-is: PhantomJS.hs
+    hs-source-dirs: test
+
+    other-modules:
+        Server
+
+    build-depends:
+        base == 4.*,
+        base-unicode-symbols >= 0.2,
+        directory >= 1.2,
+        filepath >= 1.4,
+        http-types >= 0.8,
+        network >= 2.6,
+        process >= 1.2,
+        text >= 1.2,
+        wai-cors
+
+    -- we only build the tests suite with the most recent wai version
+    -- cabal fails to resolves the old dependencies otherwise.
+    if flag (wai-1) || flag (wai-2)
+        buildable: False
+    else
+        build-depends:
+            wai-websockets >= 3.0,
+            warp >= 3.0,
+            wai >= 3.0,
+            websockets >= 0.9
+
+    ghc-options: -threaded -Wall -with-rtsopts=-N
 
