diff --git a/LICENSE b/LICENSE
new file mode 100644
--- /dev/null
+++ b/LICENSE
@@ -0,0 +1,25 @@
+The following license covers this documentation, and the source code, except
+where otherwise indicated.
+
+Copyright 2010, Nubis. All rights reserved.
+
+Redistribution and use in source and binary forms, with or without
+modification, are permitted provided that the following conditions are met:
+
+* Redistributions of source code must retain the above copyright notice, this
+  list of conditions and the following disclaimer.
+
+* Redistributions in binary form must reproduce the above copyright notice,
+  this list of conditions and the following disclaimer in the documentation
+  and/or other materials provided with the distribution.
+
+THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS "AS IS" AND ANY EXPRESS OR
+IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
+MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO
+EVENT SHALL THE COPYRIGHT HOLDERS BE LIABLE FOR ANY DIRECT, INDIRECT,
+INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT
+NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA,
+OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
+LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE
+OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF
+ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
diff --git a/README.md b/README.md
new file mode 100644
--- /dev/null
+++ b/README.md
@@ -0,0 +1,75 @@
+# TestWaiPersistent - Pragmatic integration tests for haskell web applications using WAI and Persistent
+
+yesod-test is designed for testing web applications built using wai and persistent.
+It's main goal is to encourage integration and system testing of web applications by making everything /easy to test/. 
+
+Your tests are like browser sessions that keep track of cookies and the last
+visited page. You can perform assertions on the content of HTML responses,
+using css selectors to explore the document more easily.
+
+You can also easily build requests using forms present in the current page.
+This is very useful for testing web applications built in yesod for example,
+were your forms may have field names generated by the framework or a randomly
+generated "\_token" field.
+
+Your database is also directly available so you can use runDB to set up
+backend pre-conditions, or to assert that your session is having the desired effect.
+
+The testing facilities behind the scenes are HUnit and HSpec.
+
+This is the helloworld and kitchen sink. In this case for testing a yesod app.
+
+```haskell
+
+  import Yesod
+  import Yesod.Static
+  import qualified MySite.Settings as Settings
+  import MySite.Models
+
+  main :: IO a
+  main = do
+    cfg <- (loadConfig Test) >>= either fail return
+    st <-  static Settings.staticDir
+    Settings.withConnectionPool (connStr cfg) $ \cnPool -> do
+      -- ... Perhaps some code here to truncate your test database?
+      app <- toWaiApp $ MyApp st cfg 
+      runTests app cnPool $ mySuite
+
+  mySuite = do
+    describe "Basic navigation and assertions" $ do
+      it "Gets a page that has a form, with auto generated fields and token" $ do
+        get_ "url/of/page/with/form" -- Load a page
+        statusIs 200 -- Assert the status was success
+
+        bodyContains "Hello Person" -- Assert any part of the document contains some text.
+        
+        -- Perform a css queries and assertions.
+        htmlCount "form .main" 1 -- It matches 1 element
+        htmlAllContain "h1#mainTitle" "Sign Up Now!" -- All matches have some text
+
+        -- Performs the post using the current page to extract field values:
+        post "url/to/post/to" $ do
+          addNonce -- Add the _nonce field with the currently shown value
+
+          -- Lookup field by the text on the labels pointing to them.
+          byLabel "Email:" "gustavo@cerati.com"
+          byLabel "Password:" "secret"
+          byLabel "Confirm:" "secret"
+
+      it "Sends another form, this one has a file" $ do
+        post "url/to/post/file/to" $ do
+          -- You can add files this easy, you still have to provide the mime type manually though.
+          addFile "file_field_name" "path/to/local/file" "image/jpeg"
+          
+          -- And of course you can add any field if you know it's name
+          byName "answer" "42"
+
+        statusIs 302
+
+    describe "Db access, still very raw" $ do
+      it "selects the list" $ do
+        msgs <- testDB $ do (selectList [] [] :: SqlPersist IO [(Key SqlPersist Message, Message)])
+        assertEqual "One Message in the DB" 1 (DL.length msgs)
+
+```
+
diff --git a/Setup.lhs b/Setup.lhs
new file mode 100644
--- /dev/null
+++ b/Setup.lhs
@@ -0,0 +1,7 @@
+#!/usr/bin/env runhaskell
+
+> module Main where
+> import Distribution.Simple
+
+> main :: IO ()
+> main = defaultMain
diff --git a/Yesod/Test.hs b/Yesod/Test.hs
new file mode 100644
--- /dev/null
+++ b/Yesod/Test.hs
@@ -0,0 +1,372 @@
+{-# LANGUAGE OverloadedStrings #-}
+{-# LANGUAGE PackageImports #-}
+{-|
+Yesod.Test is a pragmatic framework for testing web applications built
+using wai and persistent.
+
+By pragmatic I may also mean 'dirty'. It's main goal is to encourage integration
+and system testing of web applications by making everything /easy to test/. 
+
+Your tests are like browser sessions that keep track of cookies and the last
+visited page. You can perform assertions on the content of HTML responses,
+using css selectors to explore the document more easily.
+
+You can also easily build requests using forms present in the current page.
+This is very useful for testing web applications built in yesod for example,
+were your forms may have field names generated by the framework or a randomly
+generated '_nonce' field.
+
+Your database is also directly available so you can use runDB to set up
+backend pre-conditions, or to assert that your session is having the desired effect.
+
+-}
+
+module Yesod.Test (
+  -- * Declaring and running your test suite
+  runTests, describe, it, Specs, OneSpec,
+
+  -- * Making requests
+  -- | To make a request you need to point to an url and pass in some parameters.
+  --
+  -- To build your parameters you will use the RequestBuilder monad that lets you
+  -- add values, add files, lookup fields by label and find the current
+  -- nonce value and add it to your request too.
+  -- 
+  post, post_, get, get_, doRequest,
+  byName, fileByName,
+
+  -- | Yesod cat auto generate field ids, so you are never sure what
+  -- the argument name should be for each one of your args when constructing
+  -- your requests. What you do know is the /label/ of the field.
+  -- These functions let you add parameters to your request based
+  -- on currently displayed label names.
+  byLabel, fileByLabel,
+
+  -- | Does the current form have a _nonce? Use any of these to add it to your
+  -- request parameters.
+  addNonce, addNonce_,
+
+  -- * Running database queries
+  runDB,
+
+  -- * Assertions
+  assertEqual, statusIs, bodyContains, htmlAllContain, htmlCount,
+
+  -- * Utils for debugging tests
+  printBody, printMatches,
+
+  -- * Utils for building your own assertions
+  -- | Please consider generalizing and contributing the assertions you write.
+  htmlQuery, parseHTML
+
+)
+
+where
+
+import qualified Test.Hspec.Core as Core
+import qualified Test.Hspec.Runner as Runner
+import qualified Data.List as DL
+import qualified Data.Maybe as DY
+import qualified Data.ByteString.Char8 as BS8
+import qualified Data.Text as T
+import qualified Data.Text.Encoding as TE
+import qualified Data.ByteString.Lazy.Char8 as BSL8
+import qualified Test.HUnit as HUnit
+import qualified Test.Hspec.HUnit ()
+import qualified Network.HTTP.Types as H
+import qualified Network.Socket.Internal as Sock
+import Text.XML.HXT.Core hiding (app, err)
+import Network.Wai
+import Network.Wai.Test
+import qualified Control.Monad.Trans.State as ST
+import Control.Monad.IO.Class
+import System.IO
+import Yesod.Test.TransversingCSS
+import Database.Persist.GenericSql
+
+-- | The state used in 'describe' to build a list of specs
+data SpecsData = SpecsData Application ConnectionPool [Core.Spec]
+
+-- | The specs state monad is where 'describe' runs.
+type Specs = ST.StateT SpecsData IO ()
+
+-- | The state used in a single test case defined using 'it'
+data OneSpecData = OneSpecData Application ConnectionPool CookieValue (Maybe SResponse)
+
+-- | The OneSpec state monad is where 'it' runs.
+type OneSpec = ST.StateT OneSpecData IO
+
+data RequestBuilderData = RequestBuilderData [RequestPart] (Maybe SResponse)
+
+-- | Request parts let us discern regular key/values from files sent in the request.
+data RequestPart
+  = ReqPlainPart String String
+  | ReqFilePart String FilePath BSL8.ByteString String
+
+-- | The RequestBuilder state monad constructs an url encoded string of arguments
+-- to send with your requests. Some of the functions that run on it use the current
+-- response to analize the forms that the server is expecting to receive.
+type RequestBuilder = ST.StateT RequestBuilderData IO
+
+-- | Both the OneSpec and RequestBuilder monads hold a response that can be analized,
+-- by making them instances of this class we can have general methods that work on
+-- the last received response.
+class HoldsResponse a where
+  readResponse :: a -> Maybe SResponse
+instance HoldsResponse OneSpecData where
+  readResponse (OneSpecData _ _ _ x) = x
+instance HoldsResponse RequestBuilderData where
+  readResponse (RequestBuilderData _ x) = x
+  
+type CookieValue = H.Ascii
+
+-- | Runs your test suite, using you wai 'Application' and 'ConnectionPool' for performing 
+-- the database queries in your tests.
+--
+-- You application may already have your connection pool but you need to pass another one
+-- separately here.
+-- 
+-- Look at the examples directory on this package to get an idea of the (small) amount of
+-- boilerplate code you'll need to write before calling this.
+runTests :: Application -> ConnectionPool -> Specs -> IO a
+runTests app connection specsDef = do
+  (SpecsData _ _ specs) <- ST.execStateT specsDef (SpecsData app connection [])
+  Runner.hspecX specs
+
+-- | Start describing a Tests suite keeping cookies and a reference to the tested 'Application'
+-- and 'ConnectionPool'
+describe :: String -> Specs -> Specs
+describe label action = do
+  sData <- ST.get
+  SpecsData app conn specs <- liftIO $ ST.execStateT action sData
+  ST.put $ SpecsData app conn (Core.describe label [specs])
+
+-- | Describe a single test that keeps cookies, and a reference to the last response.
+it :: String -> OneSpec () -> Specs
+it label action = do
+  SpecsData app conn specs <- ST.get
+  let spec = Core.it label $ do
+        _ <- ST.execStateT action $ OneSpecData app conn "" Nothing
+        return ()
+  ST.put $ SpecsData app conn (specs++spec)
+
+-- Performs a given action using the last response.
+withResponse :: HoldsResponse a => (SResponse -> ST.StateT a IO b) -> ST.StateT a IO b
+withResponse f = maybe err f =<< fmap readResponse ST.get
+ where err = failure "There was no response, you should make a request"
+
+-- | Use HXT to parse a value from an html tag.
+-- Check for usage examples in this module's source.
+parseHTML :: String -> LA XmlTree a -> [a]
+parseHTML html p = runLA (hread >>> p ) html
+
+-- | Query the last response using css selectors, returns a list of matched fragments
+htmlQuery :: HoldsResponse a => Query -> ST.StateT a IO [Html]
+htmlQuery query = withResponse $ \ res ->
+  case findBySelector (BSL8.unpack $ simpleBody res) query of
+    Left err -> failure $ query ++ " did not parse: " ++ (show err)
+    Right matches -> return matches
+
+-- | Asserts that the two given values are equal.
+assertEqual :: (Eq a) => String -> a -> a -> OneSpec ()
+assertEqual msg a b = liftIO $ HUnit.assertBool msg (a == b)
+
+-- | Assert the last response status is as expected.
+statusIs :: HoldsResponse a => Int -> ST.StateT a IO ()
+statusIs number = withResponse $ \ SResponse { simpleStatus = s } ->
+  liftIO $ flip HUnit.assertBool (H.statusCode s == number) $ concat
+    [ "Expected status was ", show number
+    , " but received status was ", show $ H.statusCode s
+    ]
+
+-- | Assert the last response has the given text. The check is performed using the response
+-- body in full text form.
+bodyContains :: HoldsResponse a => String -> ST.StateT a IO ()
+bodyContains text = withResponse $ \ res ->
+  liftIO $ HUnit.assertBool ("Expected body to contain " ++ text) $
+    (simpleBody res) `contains` text
+
+contains :: BSL8.ByteString -> String -> Bool
+contains a b = DL.isInfixOf b (BSL8.unpack a)
+
+-- | Queries the html using a css selector, and all matched elements must contain
+-- the given string.
+htmlAllContain :: HoldsResponse a => Query -> String -> ST.StateT a IO ()
+htmlAllContain query search = do
+  matches <- htmlQuery query
+  case matches of
+    [] -> failure $ "Nothing matched css query: "++query
+    _ -> liftIO $ HUnit.assertBool ("Not all "++query++" contain "++search) $
+          DL.all (DL.isInfixOf search) matches
+
+-- | Performs a css query on the last response and asserts the matched elements
+-- are as many as expected.
+htmlCount :: HoldsResponse a => Query -> Int -> ST.StateT a IO ()
+htmlCount query count = do
+  matches <- fmap DL.length $ htmlQuery query
+  liftIO $ flip HUnit.assertBool (matches == count)
+    ("Expected "++(show count)++" elements to match "++query++", found "++(show matches))
+
+-- | Outputs the last response body to stderr (So it doesn't get captured by HSpec)
+printBody :: HoldsResponse a => ST.StateT a IO ()
+printBody = withResponse $ \ SResponse { simpleBody = b } -> 
+  liftIO $ hPutStrLn stderr $ BSL8.unpack b
+
+-- | Performs a CSS query and print the matches to stderr.
+printMatches :: HoldsResponse a => Query -> ST.StateT a IO ()
+printMatches query = do
+  matches <- htmlQuery query
+  liftIO $ hPutStrLn stderr $ show matches
+
+-- | Add a parameter with the given name and value.
+byName :: String -> String -> RequestBuilder ()
+byName name value = do
+  RequestBuilderData parts r <- ST.get
+  ST.put $ RequestBuilderData ((ReqPlainPart name value):parts) r
+
+-- | Add a file to be posted with the current request
+--
+-- Adding a file will automatically change your request content-type to be multipart/form-data
+fileByName :: String -> FilePath -> String -> RequestBuilder ()
+fileByName name path mimetype = do
+  RequestBuilderData parts r <- ST.get
+  contents <- liftIO $ BSL8.readFile path
+  ST.put $ RequestBuilderData ((ReqFilePart name path contents mimetype):parts) r
+
+-- This looks up the name of a field based on the contents of the label pointing to it.
+nameFromLabel :: String -> RequestBuilder String
+nameFromLabel label = withResponse $ \ res -> do
+  let
+    body = BSL8.unpack $ simpleBody res
+    escaped = escapeHtmlEntities label
+    mfor = parseHTML body $ deep $ hasName "label"
+        >>> filterA (xshow this >>> mkText >>> hasText (DL.isInfixOf escaped))
+        >>> getAttrValue "for"
+
+  case mfor of
+    for:[] -> do
+      let mname = parseHTML body $ deep $ hasAttrValue "id" (==for) >>> getAttrValue "name"
+      case mname of
+        "":_ -> failure $ "Label "++label++" resolved to id "++for++" which was not found. "
+        name:_ -> return name
+        _ -> failure $ "More than one input with id " ++ for
+    [] -> failure $ "No label contained: "++label
+    _ -> failure $ "More than one label contained "++label
+
+-- | Escape HTML entities in a string, so you can write the text you want in
+-- label lookups without worrying about the fact that yesod escapes some characters.
+escapeHtmlEntities :: String -> String 
+escapeHtmlEntities "" = ""
+escapeHtmlEntities (c:cs) = case c of
+  '<'  -> '&' : 'l' : 't' : ';'             : escapeHtmlEntities cs
+  '>'  -> '&' : 'g' : 't' : ';'             : escapeHtmlEntities cs
+  '&'  -> '&' : 'a' : 'm' : 'p' : ';'       : escapeHtmlEntities cs
+  '"'  -> '&' : 'q' : 'u' : 'o' : 't' : ';' : escapeHtmlEntities cs
+  '\'' -> '&' : '#' : '3' : '9' : ';'       : escapeHtmlEntities cs
+  x    -> x                                 : escapeHtmlEntities cs
+
+byLabel :: String -> String -> RequestBuilder ()
+byLabel label value = do
+  name <- nameFromLabel label
+  byName name value
+
+fileByLabel :: String -> FilePath -> String -> RequestBuilder ()
+fileByLabel label path mime = do
+  name <- nameFromLabel label
+  fileByName name path mime
+
+-- | Lookup a _nonce form field and add it's value to the params. 
+-- Receives a CSS selector that should resolve to the form element containing the nonce.
+addNonce_ :: String -> RequestBuilder ()
+addNonce_ scope = do
+  matches <- htmlQuery $ scope ++ "input[name=_nonce][type=hidden][value]"
+  case matches of
+    [] -> failure $ "No nonce found in the current page"
+    element:[] -> byName "_nonce" $ head $ parseHTML element $ getAttrValue "value"
+    _ -> failure $ "More than one nonce found in the page"
+
+-- | For responses that display a single form, just lookup the only nonce available.
+addNonce :: RequestBuilder ()
+addNonce = addNonce_ ""
+
+-- | Perform a POST request to url, using params
+post :: BS8.ByteString -> RequestBuilder () -> OneSpec ()
+post url paramsBuild = do
+  doRequest "POST" url paramsBuild
+
+-- | Perform a POST request without params
+post_ :: BS8.ByteString -> OneSpec ()
+post_ = flip post $ return ()
+ 
+-- | Perform a GET request to url, using params
+get :: BS8.ByteString -> RequestBuilder () -> OneSpec ()
+get url paramsBuild = doRequest "GET" url paramsBuild
+
+-- | Perform a GET request without params
+get_ :: BS8.ByteString -> OneSpec ()
+get_ = flip get $ return ()
+
+-- | General interface to performing requests, letting you specify the request method and extra headers.
+doRequest :: H.Method -> BS8.ByteString -> RequestBuilder a -> OneSpec ()
+doRequest method url paramsBuild = do
+  OneSpecData app conn cookie mRes <- ST.get
+  RequestBuilderData parts _ <- liftIO $ ST.execStateT paramsBuild $ RequestBuilderData [] mRes
+  let req = if DL.any isFile parts
+          then makeMultipart cookie parts
+          else makeSinglepart cookie parts
+
+  response <- liftIO $ runSession (srequest req) app
+  let cookie' = DY.fromMaybe cookie $ fmap snd $ DL.find (("Set-Cookie"==) . fst) $ simpleHeaders response
+  ST.put $ OneSpecData app conn cookie' (Just response)
+ where
+  isFile (ReqFilePart _ _ _ _) = True
+  isFile _ = False
+
+  -- For building the multi-part requests
+  boundary :: String
+  boundary = "*******noneedtomakethisrandom"
+  separator = BS8.concat ["--", BS8.pack boundary, "\r\n"]
+  makeMultipart cookie parts =
+    flip SRequest (BSL8.fromChunks [multiPartBody parts]) $ mkRequest
+      [ ("Cookie", cookie)
+      , ("Content-Type", BS8.pack $ "multipart/form-data; boundary=" ++ boundary)]
+  multiPartBody parts =
+    BS8.concat $ separator : [BS8.concat [multipartPart p, separator] | p <- parts]
+  multipartPart (ReqPlainPart k v) = BS8.concat
+    [ "Content-Disposition: form-data; "
+    , "name=\"", (BS8.pack k), "\"\r\n\r\n"
+    , (BS8.pack v), "\r\n"]
+  multipartPart (ReqFilePart k v bytes mime) = BS8.concat
+    [ "Content-Disposition: form-data; "
+    , "name=\"", BS8.pack k, "\"; "
+    , "filename=\"", BS8.pack v, "\"\r\n"
+    , "Content-Type: ", BS8.pack mime, "\r\n\r\n"
+    , BS8.concat $ BSL8.toChunks bytes, "\r\n"]
+
+  -- For building the regular non-multipart requests
+  makeSinglepart cookie parts = SRequest (mkRequest 
+    [("Cookie",cookie), ("Content-Type", "application/x-www-form-urlencoded")]) $
+    BSL8.pack $ DL.concat $ DL.intersperse "&" $ map singlepartPart parts
+
+  singlepartPart (ReqFilePart _ _ _ _) = ""
+  singlepartPart (ReqPlainPart k v) = concat [k,"=",v]
+
+  -- General request making 
+  mkRequest headers = defaultRequest
+    { requestMethod = method
+    , remoteHost = Sock.SockAddrInet 1 2
+    , requestHeaders = headers
+    , rawPathInfo = url
+    , pathInfo = DL.filter (/="") $ T.split (== '/') $ TE.decodeUtf8 url
+    }
+  
+-- | Run a persistent db query. For asserting on the results of performed actions
+-- or setting up pre-conditions. At the moment this part is still very raw.
+runDB :: SqlPersist IO a -> OneSpec a
+runDB query = do
+  OneSpecData _ pool _ _ <- ST.get
+  liftIO $ runSqlPool query pool
+
+-- Yes, just a shortcut
+failure :: (MonadIO a) => String -> a b
+failure reason = (liftIO $ HUnit.assertFailure reason) >> error ""
diff --git a/Yesod/Test/TransversingCSS.hs b/Yesod/Test/TransversingCSS.hs
new file mode 100644
--- /dev/null
+++ b/Yesod/Test/TransversingCSS.hs
@@ -0,0 +1,177 @@
+{- |
+This module uses HXT to transverse an HTML document using CSS selectors.
+
+The most important function here is 'findBySelector', it takes a CSS query and
+a string containing the HTML to look into,
+and it returns a list of the HTML fragments that matched the given query.
+
+Only a subset of the CSS spec is currently supported:
+
+ * By tag name: /table td a/
+ 
+ * By class names: /.container .content/
+
+ * By Id: /#oneId/
+
+ * By attribute: /[hasIt]/, /[exact=match]/, /[contains*=text]/, /[starts^=with]/, /[ends$=with]/
+ 
+ * Union: /a, span, p/
+ 
+ * Immediate children: /div > p/ 
+
+ * Get jiggy with it: /div[data-attr=yeah] > .mon, .foo.bar div, #oneThing/
+
+-}
+
+module Yesod.Test.TransversingCSS (
+  findBySelector,
+  Html,
+  Query,
+  -- * For HXT hackers
+  -- | These functions expose some low level details that you can blissfully ignore.
+  parseQuery,
+  runQuery,
+  queryToArrow,
+  Selector(..),
+  SelectorGroup(..)
+
+  )
+where
+
+import Text.XML.HXT.Core
+import qualified Data.List as DL
+import Text.ParserCombinators.Parsec
+import Text.Parsec.Prim (Parsec)
+
+type Html = String
+type Query = String
+ 
+-- | Perform a css 'Query' on 'Html'. Returns Either
+--
+-- * Left: Query parse error.
+--
+-- * Right: List of matching Html fragments.
+findBySelector :: Html-> Query -> Either ParseError [Html]
+findBySelector html query = fmap (runQuery html) (parseQuery query)
+
+-- Run a compiled query on Html, returning a list of matching Html fragments.
+runQuery :: Html -> [[SelectorGroup]] -> [Html]
+runQuery html query =
+  runLA (hread >>> (queryToArrow query) >>> xshow this) html
+
+-- | Transform a compiled query into the HXT arrow that finally transverses the Html
+queryToArrow :: ArrowXml a => [[SelectorGroup]] -> a XmlTree XmlTree
+queryToArrow commaSeparated = 
+  DL.foldl uniteCommaSeparated none commaSeparated
+ where
+  uniteCommaSeparated accum selectorGroups =
+    accum <+> (DL.foldl sequenceSelectorGroups this selectorGroups)
+  sequenceSelectorGroups accum (DirectChildren sels) =
+    accum >>> getChildren >>> (DL.foldl applySelectors this $ sels)
+  sequenceSelectorGroups accum (DeepChildren sels) =
+    accum >>> getChildren >>> multi (DL.foldl applySelectors this $ sels)
+  applySelectors accum selector = accum >>> (toArrow selector)
+  toArrow selector = case selector of
+    ById v -> hasAttrValue "id" (==v)
+    ByClass v -> hasAttrValue "class" ((DL.elem v) . words)
+    ByTagName v -> hasName v
+    ByAttrExists n -> hasAttr n
+    ByAttrEquals n v -> hasAttrValue n (==v)
+    ByAttrContains n v -> hasAttrValue n (DL.isInfixOf v)
+    ByAttrStarts n v -> hasAttrValue n (DL.isPrefixOf v)
+    ByAttrEnds n v -> hasAttrValue n (DL.isSuffixOf v)
+
+-- | Parses a query into an intermediate format which is easy to feed to HXT
+--
+-- * The top-level lists represent the top level comma separated queries.
+--
+-- * SelectorGroup is a group of qualifiers which are separated
+--   with spaces or > like these three: /table.main.odd tr.even > td.big/
+--
+-- * A SelectorGroup as a list of Selector items, following the above example
+--   the selectors in the group are: /table/, /.main/ and /.odd/ 
+parseQuery :: String -> Either ParseError [[SelectorGroup]]
+parseQuery = parse cssQuery "" 
+
+data SelectorGroup 
+  = DirectChildren [Selector]
+  | DeepChildren [Selector]
+  deriving Show
+
+data Selector
+  = ById String
+  | ByClass String
+  | ByTagName String
+  | ByAttrExists String
+  | ByAttrEquals String String
+  | ByAttrContains String String
+  | ByAttrStarts String String
+  | ByAttrEnds String String
+  deriving Show
+
+-- Below this line is the Parsec parser for css queries.
+cssQuery :: Parsec String u [[SelectorGroup]]
+cssQuery = sepBy rules (char ',' >> (optional (char ' ')))
+
+rules :: Parsec String u [SelectorGroup]
+rules = many $ directChildren <|> deepChildren
+
+directChildren :: Parsec String u SelectorGroup
+directChildren = do
+  _ <- char '>'
+  _ <- char ' '
+  sels <- selectors
+  optional $ char ' '
+  return $ DirectChildren sels
+
+deepChildren :: Parsec String u SelectorGroup
+deepChildren = do 
+  sels <- selectors
+  optional $ char ' '
+  return $ DeepChildren sels
+  
+selectors :: Parsec String u [Selector]
+selectors = many1 $ parseId
+  <|> parseClass
+  <|> parseTag
+  <|> parseAttr
+
+parseId :: Parsec String u Selector
+parseId = do
+  _ <- char '#'
+  x <- many $ noneOf ",#.[ >"
+  return $ ById x
+
+parseClass :: Parsec String u Selector
+parseClass = do
+  _ <- char '.'
+  x <- many $ noneOf ",#.[ >"
+  return $ ByClass x
+
+parseTag :: Parsec String u Selector
+parseTag = do
+  x <- many1 $ noneOf ",#.[ >"
+  return $ ByTagName x
+
+parseAttr :: Parsec String u Selector
+parseAttr = do
+  _ <- char '['
+  name <- many $ noneOf ",#.=$^*]"
+  (parseAttrExists name)
+    <|> (parseAttrWith "=" ByAttrEquals name)
+    <|> (parseAttrWith "*=" ByAttrContains name)
+    <|> (parseAttrWith "^=" ByAttrStarts name)
+    <|> (parseAttrWith "$=" ByAttrEnds name)
+
+parseAttrExists :: String -> Parsec String u Selector
+parseAttrExists attrname = do
+  _ <- char ']'
+  return $ ByAttrExists attrname
+
+parseAttrWith :: String -> (String -> String -> Selector) -> String -> Parsec String u Selector
+parseAttrWith sign constructor name = do
+  _ <- string sign
+  value <- many $ noneOf ",#.]"
+  _ <- char ']'
+  return $ constructor name value
+
diff --git a/yesod-test.cabal b/yesod-test.cabal
new file mode 100644
--- /dev/null
+++ b/yesod-test.cabal
@@ -0,0 +1,42 @@
+name:               yesod-test
+version:            0.1
+license:            BSD3
+license-file:       LICENSE
+author:             Nubis <nubis@woobiz.com.ar>
+maintainer:         Nubis <nubis@woobiz.com.ar>
+synopsis:           integration testing for WAI/Yesod Applications 
+category:           Web, Yesod, Testing
+stability:          Experimental
+cabal-version:      >= 1.6
+build-type:         Simple
+homepage:           http://www.yesodweb.com
+description:        Behaviour Oriented integration Testing for Yesod Applications 
+extra-source-files: README.md, LICENSE
+
+flag ghc7
+
+library
+    if flag(ghc7)
+        build-depends:   base                  >= 4.3      && < 5
+        cpp-options:     -DGHC7
+    else
+        build-depends:   base                  >= 4        && < 4.3
+    build-depends:   hxt >= 9.1.6
+                   , parsec                    >= 2.1      && < 4
+                   , persistent                >= 0.8      && < 0.9
+                   , transformers              >= 0.2.2    && < 0.3
+                   , wai                       >= 1.1      && < 1.2
+                   , wai-test                  >= 1.0      && < 2.0
+                   , network                   >= 2.2      && < 2.4
+                   , http-types                >= 0.6      && < 0.7
+                   , HUnit                     >= 1.2      && < 1.3
+                   , hspec                     >= 0.9      && < 1.0
+                   , bytestring                >= 0.9
+                   , text
+    exposed-modules: Yesod.Test
+    other-modules: Yesod.Test.TransversingCSS
+    ghc-options:  -Wall
+
+source-repository head
+  type: git
+  location: git://github.com/yesodweb/yesod.git
