diff --git a/stack.yaml b/stack.yaml
index 8078ababc..990f44e2e 100644
--- a/stack.yaml
+++ b/stack.yaml
@@ -14,3 +14,4 @@ packages:
- ./yesod
- ./yesod-eventsource
- ./yesod-websockets
+- ./yesod-hspec
diff --git a/yesod-hspec/ChangeLog.md b/yesod-hspec/ChangeLog.md
new file mode 100644
index 000000000..a343b3660
--- /dev/null
+++ b/yesod-hspec/ChangeLog.md
@@ -0,0 +1,167 @@
+# ChangeLog for yesod-test
+
+## 1.6.12
+
+* Fix import in cookie example [#1713](https://github.com/yesodweb/yesod/pull/1713)
+* Add `MonadState` instance for `SIO`
+
+## 1.6.11
+
+* Add missing `HasCallStack`s [#1710](https://github.com/yesodweb/yesod/pull/1710)
+
+## 1.6.10
+
+* `statusIs` assertion failures now print a preview of the response body, if the response body is UTF-8 or ASCII. [#1680](https://github.com/yesodweb/yesod/pull/1680/files)
+* Adds an `Yesod.Test.Internal`, which exposes functions that yesod-test uses. These functions do _not_ constitute a stable API.
+
+## 1.6.9.1
+
+* Improve documentation [#1676](https://github.com/yesodweb/yesod/pull/1676)
+* Require GHC 8.2 (base >= 4.10)
+
+## 1.6.9
+
+Add `requireJSONResponse` function [#1646](https://github.com/yesodweb/yesod/pull/1646)
+
+## 1.6.8
+
+Add `testModifySite` function [#1642](https://github.com/yesodweb/yesod/pull/1642)
+
+## 1.6.7
+
+Add `addBasicAuthHeader` function [#1632](https://github.com/yesodweb/yesod/pull/1632)
+
+## 1.6.6.2
+
+addPostParam will now URL-encode keys and values to prevent corruption
+when special characters such as `&` are used
+[#1617](https://github.com/yesodweb/yesod/pull/1617)
+
+## 1.6.6.1
+
+* Documentation fixes
+* Support for network 3
+
+## 1.6.6
+
+* Add utility functions to modify cookies [$1570](https://github.com/yesodweb/yesod/pull/1570)
+
+## 1.6.5.1
+
+* Make test suite build with GHC 8.6 [#1561](https://github.com/yesodweb/yesod/pull/1561)
+
+## 1.6.5
+bodyEquals prints out actual body in addition to expected body in failure msg
+[#1525](https://github.com/yesodweb/yesod/pull/1525)
+
+## 1.6.4
+Add yesodSpecWithSiteGeneratorAndArgument
+[#1485](https://github.com/yesodweb/yesod/pull/1485)
+
+## 1.6.3
+Add performMethod
+[#1502](https://github.com/yesodweb/yesod/pull/1502)
+
+## 1.6.2
+
+* Add byLabel-related functions like byLabelContain
+[#1482](https://github.com/yesodweb/yesod/pull/1482)
+
+## 1.6.1
+
+* Fix the build with `base-4.11` (GHC 8.4).
+
+## 1.6.0
+
+* Upgrade to yesod-core 1.6.0
+
+## 1.5.9.1
+
+* Fixes a Haddock syntax error in 1.5.9 [#1473](https://github.com/yesodweb/yesod/pull/1473)
+
+## 1.5.9
+* Add byLabelExact and related functions
+[#1459](https://github.com/yesodweb/yesod/pull/1459)
+
+## 1.5.8
+* Added implicit parameter HasCallStack to assertions.
+[#1421](https://github.com/yesodweb/yesod/pull/1421)
+
+## 1.5.7
+
+* Add clickOn.
+[#1408](https://github.com/yesodweb/yesod/pull/1408)
+
+## 1.5.6
+
+* Add assertNotEq.
+[#1375](https://github.com/yesodweb/yesod/pull/1375)
+
+## 1.5.5
+
+* Fix warnings
+
+## 1.5.4.1
+
+* Compilation fix for GHC 7.8
+
+## 1.5.4
+
+* yesod-test: add getLocation test helper. [#1314](https://github.com/yesodweb/yesod/pull/1314)
+
+## 1.5.3
+
+* Added bodyNotContains [#1271](https://github.com/yesodweb/yesod/pull/1271)
+
+## 1.5.2
+
+* Added assertEq, deprecated assertEqual [#1259](https://github.com/yesodweb/yesod/pull/1259)
+
+## 1.5.1.1
+
+* Fix `addToken_` needing a trailing space and allows multiples spaces in css selector.
+
+## 1.5.1.0
+
+* Better error provenance for stuff invoking withResponse' [#1191](https://github.com/yesodweb/yesod/pull/1191)
+
+## 1.5.0.1
+
+* Fixed the `application/x-www-form-urlencoded` header being added to all requests, even those sending a binary POST body [#1064](https://github.com/yesodweb/yesod/pull/1064/files)
+ * The `application/x-www-form-urlencoded` Content-Type header is now only added if key-value POST parameters are added
+ * If no key-values pairs are added, or the request body is set with `setRequestBody`, no default Content-Type header is set
+
+## 1.5
+
+* remove deprecated addNonce functions
+* You can now configure testing middleware
+
+Configuring middleware makes it easy to add logging among other things.
+middleware is applied to the wai app before each test.
+
+If you follow the yesod scaffold, you probably have a
+withApp function in TestImport.hs.
+This function should now return (foundation, middleware).
+`id` is an acceptable value for middleware.
+
+
+## 1.4.4
+
+test helpers for CRSF middleware such as addTokenFromCookie
+
+## 1.4.3.2
+
+* Add `addTokenFromCookie` and `addTokenFromCookieNamedToHeaderNamed`, which support the new CSRF token middleware [#1058](https://github.com/yesodweb/yesod/pull/1058)
+* Add `getRequestCookies`, which returns the cookies from the most recent request [#1058](https://github.com/yesodweb/yesod/pull/1058)
+
+## 1.4.3.1
+
+* Improved README
+
+## 1.4.2
+
+Provide `Example` instance for `YesodExample`.
+
+## 1.4.1.1
+
+Upgrade to hspec 2
diff --git a/yesod-hspec/LICENSE b/yesod-hspec/LICENSE
new file mode 100644
index 000000000..d9f041796
--- /dev/null
+++ b/yesod-hspec/LICENSE
@@ -0,0 +1,20 @@
+Copyright (c) 2012 Michael Snoyman, http://www.yesodweb.com/
+
+Permission is hereby granted, free of charge, to any person obtaining
+a copy of this software and associated documentation files (the
+"Software"), to deal in the Software without restriction, including
+without limitation the rights to use, copy, modify, merge, publish,
+distribute, sublicense, and/or sell copies of the Software, and to
+permit persons to whom the Software is furnished to do so, subject to
+the following conditions:
+
+The above copyright notice and this permission notice shall be
+included in all copies or substantial portions of the Software.
+
+THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
+EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
+MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
+NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
+LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
+OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
+WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
diff --git a/yesod-hspec/README.md b/yesod-hspec/README.md
new file mode 100644
index 000000000..5f7aa9a36
--- /dev/null
+++ b/yesod-hspec/README.md
@@ -0,0 +1,67 @@
+# yesod-test
+
+Pragmatic integration tests for haskell web applications using WAI and optionally a database (Persistent).
+
+Its 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.
+
+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,
+where your forms may have field names generated by the framework or a randomly
+generated CSRF "\_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 HSpec (on top of HUnit).
+
+The code sample below covers the core concepts of yesod-test. Check out the
+[yesod-scaffolding for usage in a complete application](https://github.com/yesodweb/yesod-scaffold/tree/postgres/test).
+
+```haskell
+spec :: Spec
+spec = withApp $ do
+ describe "Basic navigation and assertions" $ do
+ it "Gets a page that has a form, with auto generated fields and token" $ do
+ get ("url/to/page/with/form" :: Text) -- Load a page.
+ statusIs 200 -- Assert the status was success.
+
+ bodyContains "Hello Person" -- Assert any part of the document contains some text.
+
+ -- Perform 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:
+ request $ do
+ setMethod "POST"
+ setUrl SignupR
+ addToken -- Add the CSRF _token 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
+ request $ do
+ setMethod "POST"
+ setUrl ("url/to/post/file/to" :: Text)
+ -- You can easily add files, though you still need to provide the MIME type for them.
+ addFile "file_field_name" "path/to/local/file" "image/jpeg"
+
+ -- And of course you can add any field if you know its name.
+ addPostParam "answer" "42"
+
+ statusIs 302
+
+ describe "Database access" $ do
+ it "selects the list" $ do
+ -- See the Yesod scaffolding for the runDB implementation
+ msgs <- runDB $ selectList ([] :: [Filter Message]) []
+ assertEqual "One Message in the DB" 1 (length msgs)
+```
+
diff --git a/yesod-hspec/Setup.lhs b/yesod-hspec/Setup.lhs
new file mode 100755
index 000000000..06e2708f2
--- /dev/null
+++ b/yesod-hspec/Setup.lhs
@@ -0,0 +1,7 @@
+#!/usr/bin/env runhaskell
+
+> module Main where
+> import Distribution.Simple
+
+> main :: IO ()
+> main = defaultMain
diff --git a/yesod-hspec/Yesod/Hspec.hs b/yesod-hspec/Yesod/Hspec.hs
new file mode 100644
index 000000000..d97fb1a25
--- /dev/null
+++ b/yesod-hspec/Yesod/Hspec.hs
@@ -0,0 +1,1593 @@
+{-# LANGUAGE DerivingStrategies, GeneralizedNewtypeDeriving #-}
+{-# LANGUAGE OverloadedStrings #-}
+{-# LANGUAGE FlexibleContexts #-}
+{-# LANGUAGE RecordWildCards #-}
+{-# LANGUAGE FlexibleInstances #-}
+{-# LANGUAGE TypeFamilies #-}
+{-# LANGUAGE ImplicitParams #-}
+{-# LANGUAGE ConstraintKinds #-}
+{-# LANGUAGE GeneralizedNewtypeDeriving #-}
+{-# LANGUAGE MultiParamTypeClasses #-}
+
+{-|
+Yesod.Hspec is a pragmatic framework for testing web applications built
+using wai.
+
+By pragmatic I may also mean 'dirty'. Its 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,
+where your forms may have field names generated by the framework or a randomly
+generated CSRF token input.
+
+=== Example project
+
+The best way to see an example project using yesod-test is to create a scaffolded Yesod project:
+
+@stack new projectname yesod-sqlite@
+
+(See https://github.com/commercialhaskell/stack-templates/wiki#yesod for the full list of Yesod templates)
+
+The scaffolded project makes your database directly available in tests, so you can use 'runDB' to set up
+backend pre-conditions, or to assert that your session is having the desired effect.
+It also handles wiping your database between each test.
+
+=== Example code
+
+The code below should give you a high-level idea of yesod-test's capabilities.
+Note that it uses helper functions like @withApp@ and @runDB@ from the scaffolded project; these aren't provided by yesod-test.
+
+@
+spec :: Spec
+spec = withApp $ do
+ describe \"Homepage\" $ do
+ it "loads the homepage with a valid status code" $ do
+ 'get' HomeR
+ 'statusIs' 200
+ describe \"Login Form\" $ do
+ it "Only allows dashboard access after logging in" $ do
+ 'get' DashboardR
+ 'statusIs' 401
+
+ 'get' HomeR
+ -- Assert a \
tag exists on the page
+ 'htmlAnyContain' \"p\" \"Login\"
+
+ -- yesod-test provides a 'RequestBuilder' monad for building up HTTP requests
+ 'request' $ do
+ -- Lookup the HTML \