diff --git a/CHANGELOG.md b/CHANGELOG.md
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -1,66 +1,84 @@
 # Change Log
 
+## 0.14.0.0
+* Fix MVar bug in teardownWebDriverContext.
+* Make driverConfigLogDir optional (#206).
+* Add "Test.WebDriver.WD" module for simple usage.
+
 ## 0.13.0.0
-* Deprecated function `elemInfo` removed
-* `deleteVisibleCookies` renamed to `deleteCookies`.
+* Added support for Selenium 3 and 4 with W3C WebDriver protocol compatibility.
+* Dropped legacy Selenium JSON wire protocol support.
+* Reworked [createSession](https://hackage.haskell.org/package/webdriver-0.12.0.0/docs/Test-WebDriver-Commands.html#v:createSession) into [startSession](https://hackage-content.haskell.org/package/webdriver-0.13.0.0/docs/Test-WebDriver.html#v:startSession), which uses [WebDriverContext](https://hackage-content.haskell.org/package/webdriver-0.13.0.0/docs/Test-WebDriver-Types.html#t:WebDriverContext) for process lifecycle management.
+* Replaced [WebDriver](https://hackage.haskell.org/package/webdriver-0.12.0.0/docs/Test-WebDriver-Class.html#t:WebDriver) class with [WebDriverBase](https://hackage-content.haskell.org/package/webdriver-0.13.0.0/docs/Test-WebDriver-Types.html#t:WebDriverBase) and switched from `MonadBaseControl IO` to `MonadUnliftIO`.
+* Simplified module hierarchy with centralized imports under `Test.WebDriver`.
+* Enhanced browser profile handling with in-memory zip archiving instead of on-disk preparation.
+* Added comprehensive test suite with matrix testing across Selenium versions and browsers.
+* Added browser log retrieval functions: [withRecordLogsViaBiDi](https://hackage-content.haskell.org/package/webdriver-0.13.0.0/docs/Test-WebDriver-Commands.html#v:withRecordLogsViaBiDi) and [getLogs](https://hackage-content.haskell.org/package/webdriver-0.13.0.0/docs/Test-WebDriver-Commands.html#v:getLogs).
+* Now you can manage the script, page load, and implicit wait timeouts in a single operation with [getTimeouts](https://hackage-content.haskell.org/package/webdriver-0.13.0.0/docs/Test-WebDriver-Commands.html#v:getTimeouts)/[setTimeouts](https://hackage-content.haskell.org/package/webdriver-0.13.0.0/docs/Test-WebDriver-Commands.html#v:setTimeouts).
+* The [getWindowSize](https://hackage.haskell.org/package/webdriver-0.12.0.0/docs/Test-WebDriver-Commands.html#v:getWindowSize)/[setWindowSize](https://hackage.haskell.org/package/webdriver-0.12.0.0/docs/Test-WebDriver-Commands.html#v:setWindowSize)/[getWindowPos](https://hackage.haskell.org/package/webdriver-0.12.0.0/docs/Test-WebDriver-Commands.html#v:getWindowPos)/[setWindowPos](https://hackage.haskell.org/package/webdriver-0.12.0.0/docs/Test-WebDriver-Commands.html#v:setWindowPos) functions have been replaced with [getWindowRect](https://hackage-content.haskell.org/package/webdriver-0.13.0.0/docs/Test-WebDriver-Commands.html#v:getWindowRect)/[setWindowRect](https://hackage-content.haskell.org/package/webdriver-0.13.0.0/docs/Test-WebDriver-Commands.html#v:setWindowRect).
+* The [elemPos](https://hackage.haskell.org/package/webdriver-0.12.0.0/docs/Test-WebDriver-Commands.html#v:elemPos)/[elemSize](https://hackage.haskell.org/package/webdriver-0.12.0.0/docs/Test-WebDriver-Commands.html#v:elemSize) functions have been replaced with [elemRect](https://hackage-content.haskell.org/package/webdriver-0.13.0.0/docs/Test-WebDriver-Commands.html#v:elemRect).
+* [deleteCookieByName](https://hackage.haskell.org/package/webdriver-0.12.0.0/docs/Test-WebDriver-Commands.html#v:deleteCookieByName) has been renamed to [deleteCookie](https://hackage-content.haskell.org/package/webdriver-0.13.0.0/docs/Test-WebDriver-Commands.html#v:deleteCookie) and the pointless old [deleteCookie](https://hackage.haskell.org/package/webdriver-0.12.0.0/docs/Test-WebDriver-Commands.html#v:deleteCookie) removed.
+* [screenshotBase64](https://hackage.haskell.org/package/webdriver-0.12.0.0/docs/Test-WebDriver-Commands.html#v:screenshotBase64) has been removed and [screenshotElement](https://hackage-content.haskell.org/package/webdriver-0.13.0.0/docs/Test-WebDriver-Commands.html#v:screenshotElement) added.
+* [deleteVisibleCookies](https://hackage.haskell.org/package/webdriver-0.12.0.0/docs/Test-WebDriver-Commands.html#v:deleteVisibleCookies) renamed to [deleteCookies](https://hackage-content.haskell.org/package/webdriver-0.13.0.0/docs/Test-WebDriver-Commands.html#v:deleteCookies).
+* Deprecated function [elemInfo](https://hackage.haskell.org/package/webdriver-0.12.0.0/docs/Test-WebDriver-Commands.html#v:elemInfo) removed.
 
 ## 0.12.0.1
 * Switch from `data-default-class` to `data-default` to address https://github.com/commercialhaskell/stackage/issues/7545. This is weirdly complicated: it looks like versions of `data-default` from `0.5.2` to `0.8.0.0` may pull in unnecessary (to us) `data-default-instances-*` dependencies. However, what we've done here should build with all versions, so we won't worry about it. At the next breaking change I'd like to remove `data-default` entirely.
 
 ## 0.12.0.0
-* Support aeson-2.2.0
+* Support aeson-2.2.0.
 
 ## 0.11.0.0
-* Support GHC 9.6
-* Fix a link to the wrong GitHub branch in package description
-* Remove instances for deprecated ListT and ErrorT
+* Support GHC 9.6.
+* Fix a link to the wrong GitHub branch in package description.
+* Remove instances for deprecated ListT and ErrorT.
 
 ## 0.10.0.1
-* Update links to reflect this package's new home at https://github.com/haskell-webdriver/haskell-webdriver/
+* Update links to reflect this package's new home at https://github.com/haskell-webdriver/haskell-webdriver/.
 
 ## 0.10.0.0
-* Add Aeson 2 compatibility to support GHC 9.0 and 9.2
-* Derive MonadMask instance for the WD monad
-* Fix parameter name in the `focusWindow` function
-* Fix Cookie ToJSON instance
-* Convert a couple noReturn calls to ignoreReturn to avoid a crash
-* Switch to hpack
-* Switch to GitHub Actions CI
-* Remove cabal flags and shorten extra-source-files
+* Add Aeson 2 compatibility to support GHC 9.0 and 9.2.
+* Derive MonadMask instance for the WD monad.
+* Fix parameter name in the `focusWindow` function.
+* Fix Cookie ToJSON instance.
+* Convert a couple noReturn calls to ignoreReturn to avoid a crash.
+* Switch to hpack.
+* Switch to GitHub Actions CI.
+* Remove cabal flags and shorten extra-source-files.
 
 ## 0.9.0.1
-* Fixed build errors when building against aeson-1.4.3.0
+* Fixed build errors when building against aeson-1.4.3.0.
 
 ## 0.9
 
 ### Breaking API changes
-* Changed the type of the `cookExpiry` field of `Cookie` from `Maybe Integer` to `Maybe Double`. This fixes parsing issues with some Selenium server implementations
+* Changed the type of the `cookExpiry` field of `Cookie` from `Maybe Integer` to `Maybe Double`. This fixes parsing issues with some Selenium server implementations.
 * Changed `elemPos` and `elemSize` to return `Float` pairs instead of `Int` pairs. This fixes parsing issues with some Selemium server implementations.
 * Removed the `Element` argument from the `sendRawKeys` function. This argument is not used in modern Selenium versions.
 
 ### New API features
 * Added a `LogDebug` constructor to the `LogLevel` type.
 * Added `ffAccpetInsecureCerts` capability for `Firefox` geckodriver.
-* The constructor for `ExpectFailed` is now exported so that it can be caught properly by exception handlers
+* The constructor for `ExpectFailed` is now exported so that it can be caught properly by exception handlers.
 * Added GHC callstack support. `BadJSON` exceptions are now caught and rethrown with `error` calls to improve stack traces.
 
 ### W3C standard compatibility fixes
-* Fixed `ToJSON Element` instance so that it accepts both old OSS and new W3C element format (fixes compatibility issue with Selenium 3+ versions)
-* Changed the `maximize` API call to use POST instead of GET
+* Fixed `ToJSON Element` instance so that it accepts both old OSS and new W3C element format (fixes compatibility issue with Selenium 3+ versions).
+* Changed the `maximize` API call to use POST instead of GET.
 
 ### Other Selenium compatibility fixes
-* Fixed an error with some versions of chromedriver when using `closeWindow`
+* Fixed an error with some versions of chromedriver when using `closeWindow`.
 
 ## 0.8.5
-* Added support for experimental Chrome options that are not part of the API
-* Added a Phantomjs constructor for the Browser type
-* Changed the type of StackFrame line numbers from Word to Int to avoid parse errors in newer Aeson versions (sometimes the server returns negative line numbers)
-* Fixed support for http-client > 0.5
+* Added support for experimental Chrome options that are not part of the API.
+* Added a Phantomjs constructor for the Browser type.
+* Changed the type of StackFrame line numbers from Word to Int to avoid parse errors in newer Aeson versions (sometimes the server returns negative line numbers).
+* Fixed support for http-client > 0.5.
 
 ## 0.8.4
 * Added a new `Test.WebDriver.Common.Keys` module with named constants for use with `sendKeys`
-* Updated old URLs in documentation
-* Introduced support for http-client 5.0
+* Updated old URLs in documentation.
+* Introduced support for http-client 5.0.
 
 ## 0.8.3
 * Removed most upper bounds on dependencies in our cabal file to avoid stackage version madness.
@@ -70,19 +88,19 @@
 * Added new `WebDriver` instance for `ExceptT`.
 
 ## 0.8.1
-* Previously internal convenience functions `noReturn` and `ignoreReturn` are now exported in Test.WebDriver.JSON
+* Previously internal convenience functions `noReturn` and `ignoreReturn` are now exported in Test.WebDriver.JSON.
 * `elemInfo` is now deprecated due to it being phased out in the Marionette (Firefox) driver. It will likely be removed once Selenium 4 is released.
 * Fixed an issue causing PAC settings to not work.
 
 ## 0.8.0.4
-* Quick fix to parse "unsupported command" errors when using Marionette driver (Selenium + Marionette has nonstandard behavior when reporting that error type)
+* Quick fix to parse "unsupported command" errors when using Marionette driver (Selenium + Marionette has nonstandard behavior when reporting that error type).
 
 ## 0.8.0.3
-* Fixed build errors for GHC < 7.10. webdriver now builds with GHC stable releases 7.4.2, 7.6.3, and 7.8.4
+* Fixed build errors for GHC < 7.10. webdriver now builds with GHC stable releases 7.4.2, 7.6.3, and 7.8.4.
 * Fixed support for bytestring 0.9.*
 
 ## 0.8.0.2
-* Fixed issue introduced in 0.8 that caused build failure when using aeson < 0.10
+* Fixed issue introduced in 0.8 that caused build failure when using aeson < 0.10.
 
 ## 0.8.0.1
 * findElems and and findElemsFrom were accidentally changed to return a single Element in the last release. This has been fixed.
@@ -115,15 +133,15 @@
 
 ### Reworked custom HTTP headers interface
 * Support for custom request headers was added rather hastily, resulting in several functions having explicit `RequestHeaders` parameters. The interface has been reworked now so that custom request headers are stored inside `WDSession` and explicit `RequestHeaders` parameters have been removed.
-* There's also now a distinction in configuration between `wdAuthHeaders` which are used only during initial session creation, and `wdRequestHeaders`, which are used with all other HTTP requests
-* Two new utility functions were added to make working with custom HTTP headers easier: `withRequestHeaders` and `withAuthHeaders`
+* There's also now a distinction in configuration between `wdAuthHeaders` which are used only during initial session creation, and `wdRequestHeaders`, which are used with all other HTTP requests.
+* Two new utility functions were added to make working with custom HTTP headers easier: `withRequestHeaders` and `withAuthHeaders`.
 
 ### Clean-up and dependency changes
 * Removed a whole mess of unusued import and deprecation warnings when building with GHC 7.10
 * We now enforce an attoparsec lower bound of 0.10 (there was no lower bound before)
 * The unnecessary dependency on mtl is now removed.
-* Added some monad transformer instances for WebDriver and WDSessionState that were mysteriously missing: strict WriterT, ReaderT, ListT
-* data-default dependency was changed to data-default-class
+* Added some monad transformer instances for WebDriver and WDSessionState that were mysteriously missing: strict WriterT, ReaderT, ListT.
+* data-default dependency was changed to data-default-class.
 
 ## 0.7
 
@@ -133,8 +151,8 @@
 
 ### Top-level API (exposed by`Test.WebDriver` module)
   * `withSession` is now overloaded over the new constraint alias `WDSessionStateControl` ([see below](#typeclass-api)). This means that it can be used with monad transformer stacks over `WD` without any lifting required.
-  * Added several new `WDConfig` convenience functions: `modifyCaps`, `useBrowser`, `useVersion`, `usePlatform`, `useProxy`; these functions are overloaded over the new `HasCapabilities` constraint alias ([see below](#typeclass-api))
-  * Reworked and improved session history API
+  * Added several new `WDConfig` convenience functions: `modifyCaps`, `useBrowser`, `useVersion`, `usePlatform`, `useProxy`; these functions are overloaded over the new `HasCapabilities` constraint alias ([see below](#typeclass-api)).
+  * Reworked and improved session history API.
     * Added a `SessionHistory` record type to replace the old `(Request, Response ByteString)` type. The new type has the same data as the previous tuple, but additionally records the number of attempted HTTP retries, and instead of `Response ByteString` uses `Either SomeException (Response ByteString)` so that HTTP request errors can be logged.
     * Removed `wdKeepSessHist` field from `WDConfig` and replaced it with `wdHistoryConfig`, which uses a new `SessionHistoryConfig` type.
 
@@ -155,7 +173,7 @@
       ```
 
 ### Implicit waits API (`Test.WebDriver.Commands.Wait`)
-* Added functions for checking some expected conditions in explicit waits: `expectNotStale`, `expectAlertOpen`, `catchFailedCommand`
+* Added functions for checking some expected conditions in explicit waits: `expectNotStale`, `expectAlertOpen`, `catchFailedCommand`.
 
 ### Typeclass API
 * `WDSessionState` is now a superclass of `Monad` and `Applicative` instead of `MonadBaseControl IO`. This makes the class significantly more flexible in how it can be used, as it no longer requires `IO` as a base monad.
@@ -186,45 +204,45 @@
 
 ### Minor API changes (not exposed to `Test.WebDriver` module)
 * `Test.WebDriver.Session` changes
-  * new function `mostRecentHistory` added
-  * `lastHTTPRequest` renamed to `mostRecentHTTPRequest` (for consistency)
-  * `mkSession` moved from `Test.WebDriver.Config` to `Test.WebDriver.Session`
+  * new function `mostRecentHistory` added.
+  * `lastHTTPRequest` renamed to `mostRecentHTTPRequest` (for consistency).
+  * `mkSession` moved from `Test.WebDriver.Config` to `Test.WebDriver.Session`.
 * `Test.WebDriver.Internal` changes
   * `sendHTTPRequest` now returns `(Either SomeException (Response ByteString))` and catches any exceptions that occur during the request. When using default configuration, the exceptions are also saved in 'wdSessHist'.
   * `Test.WebDriver.Internal` no longer defines and exports exception types. All exception defined here previously have been moved to an unexposed `Test.WebDriver.Exceptions.Internal` module. These types are still exported in the usual places.
 
 ### Dependencies
-* Now supports http-types up to 0.9
+* Now supports http-types up to 0.9.
 
 ## 0.6.3.1
-* Fixed an issue with aeson 0.10 support
+* Fixed an issue with aeson 0.10 support.
 
 ## 0.6.3
-* Support aeson 0.10
-* Added support for multiple HTTP attempts per command request, using the new WDConfig field wdHTTPRetryCount
+* Support aeson 0.10.
+* Added support for multiple HTTP attempts per command request, using the new WDConfig field wdHTTPRetryCount.
 
 ## 0.6.2.1
-* Supports vector 0.11, aeson 0.9, attoparsec 0.13
+* Supports vector 0.11, aeson 0.9, attoparsec 0.13.
 
 ## 0.6.2
-* Supports GHC 7.10
-* Supports reworked Chrome capabilities used by newer versions of WebDriver
-* Servers that return empty JSON strings for commands with no return value will no longer cause parse errors
+* Supports GHC 7.10.
+* Supports reworked Chrome capabilities used by newer versions of WebDriver.
+* Servers that return empty JSON strings for commands with no return value will no longer cause parse errors.
 
 ## 0.6.1
-* Added the ability to pass HTTP request headers at session creation
-* Fixed an issue involving an obsolete JSON representation of Chrome capabilities
-* Relax upper bound on exceptions dependency
+* Added the ability to pass HTTP request headers at session creation.
+* Fixed an issue involving an obsolete JSON representation of Chrome capabilities.
+* Relax upper bound on exceptions dependency.
 
 ## 0.6.0.4
-* Support for monad-control 1.0
+* Support for monad-control 1.0.
 
 ## 0.6.0.3
-* Relaxed upper bounds on text and http-client versions
+* Relaxed upper bounds on text and http-client versions.
 
 ## 0.6.0.2
-* Added support for aeson > 0.8 and network > 2.6
-* Added support for the "X-Response-Body-Start" HTTP header used for error responses in newer http-client versions
+* Added support for aeson > 0.8 and network > 2.6.
+* Added support for the "X-Response-Body-Start" HTTP header used for error responses in newer http-client versions.
 
 ## 0.6.0.1
 * Fixed Haddock parse errors. No code changes introduced in this version.
@@ -232,10 +250,10 @@
 ## 0.6
 * Rather than WDSession serving dual roles as configuration and state, its functionality has been split into 2 respective types: WDConfig and WDSession.
 * runSession now takes a WDConfig instead of WDSession and Capabilities parameters.
-* runSession no longer closes its session on successful completion; use finallyClose or closeOnException for this behavior
-* The old Test.WebDriver.Classes module has been split into Test.WebDriver.Session and Test.WebDriver.Class
-* SessionState typeclass renamed to WDSessionState
-* We now use the http-client package instead of HTTP. This is reflected in the addition of Manager fields in both WDConfig and WDSession
+* runSession no longer closes its session on successful completion; use finallyClose or closeOnException for this behavior.
+* The old Test.WebDriver.Classes module has been split into Test.WebDriver.Session and Test.WebDriver.Class.
+* SessionState typeclass renamed to WDSessionState.
+* We now use the http-client package instead of HTTP. This is reflected in the addition of Manager fields in both WDConfig and WDSession.
 
 ## 0.5.5
 * Added optional HTTP history tracking for debugging purposes.
@@ -245,50 +263,50 @@
 * Relaxed dependencies on mtl, network and scientific.
 
 ## 0.5.3.3
-* Relaxed text dependency up to 1.2
+* Relaxed text dependency up to 1.2.
 
 ## 0.5.3.2
 ### bug fixes
-* fixed remaining compilation problems with aeson. now supports aeson >= 0.6.2.0 && < 0.8
+* fixed remaining compilation problems with aeson. now supports aeson >= 0.6.2.0 && < 0.8.
 * now depends on directory-tree instead of filesystem-trees. this fixes several problems with firefox/chrome profile support.
 
 ## 0.5.3.1
 ### bug fixes
-* fixed compilation error with aeson 0.7 and greater
+* fixed compilation error with aeson 0.7 and greater.
 
 ## 0.5.3
 
 ### new features
-* SessionNotCreated constructor added to FailedCommandType
-* new command deleteCookieByName added
+* SessionNotCreated constructor added to FailedCommandType.
+* new command deleteCookieByName added.
 
 ###b ug fixes
-* asyncJS now properly distinguishes between a null return and a script timeout
-* fixed a change in waitWhile causing the opposite of expected behavior
+* asyncJS now properly distinguishes between a null return and a script timeout.
+* fixed a change in waitWhile causing the opposite of expected behavior.
 
 ## 0.5.2
 ### API changes
-* added many new Internet Explorer capabilities
-* added additionalCaps to Capabilities, which allows support for non-standard capabilities
-* Browser type now supports non-standard browsers via the new Browser constructor
-* added support for the new unexpectedAlertBehaviour capability
+* added many new Internet Explorer capabilities.
+* added additionalCaps to Capabilities, which allows support for non-standard capabilities.
+* Browser type now supports non-standard browsers via the new Browser constructor.
+* added support for the new unexpectedAlertBehaviour capability.
 
 ### new features
-* new command getApplicationCacheStatus supported
-* error reporting for unknown commands now slightly improved
+* new command getApplicationCacheStatus supported.
+* error reporting for unknown commands now slightly improved.
 
 ### bug fixes
-* internal request URIs are now properly percent-encoded
-* improved handling of browser-specific capabilities
-* fixed incompatability with new session creation protocol changes introduced in selenium 2.35
-* updated to work with Aeson 0.6.2 and onward
+* internal request URIs are now properly percent-encoded.
+* improved handling of browser-specific capabilities.
+* fixed incompatability with new session creation protocol changes introduced in selenium 2.35.
+* updated to work with Aeson 0.6.2 and onward.
 
 ## hs-webdriver 0.5.1
 ### API changes
 * Test.WebDriver.Internal.FailedCommandInfo now stores screenshots as lazy bytestrings
 * Test.WebDriver.Common.Profile now stores PreparedProfile as a lazy bytestring
 * Test.WebDriver.Chrome.Extension now stores ChromeExtension as a lazy bytestring
-* The LogPref type has been renamed to LogLevel to reflect its use within the new log interface
+* The LogPref type has been renamed to LogLevel to reflect its use within the new log interface.
 
 ### new features
 * a new log interface as specified by the webdriver standard. This includes the functions getLogs and getLogTypes, and the types LogType and LogEntry.
@@ -318,7 +336,7 @@
 
 ### bug fixes
 * fixed a typo in the export list of Firefox.Profile; deleteFile is now correctly exported instead of removeFile from System.Directory
-* fixed an error in the JSON representation of MouseButton
+* fixed an error in the JSON representation of MouseButton.
 
 ### new features
 * A new module, Test.WebDriver.Commands.Internal, which exports some low-level functions used to implement the high-level interface. This makes it possible for library users to extend hs-webdriver with nonstandard or unimplemented features.
@@ -339,20 +357,20 @@
 * It's now possible to add entire directories to a profile in pure code using addFile and addExtension.
 * new functions in Common.Profile: unionProfiles, onProfileFiles, onProfilePrefs
 * new function in Commands.Wait: onTimeout
-* the WD monad now has a MonadCatchIO instance, as an alternative to lifted-base for exception handling
+* the WD monad now has a MonadCatchIO instance, as an alternative to lifted-base for exception handling.
 
 
 ## hs-webdriver 0.3.2.1
 
 ### bug fixes
-* Removed a bug in waitWhile' that resulted in an infinite loop
-* Fixed the incorrect representation of JSON profiles
-* Fixed relative path issues when zipping profile directories from disk
+* Removed a bug in waitWhile' that resulted in an infinite loop.
+* Fixed the incorrect representation of JSON profiles.
+* Fixed relative path issues when zipping profile directories from disk.
 
 ## hs-webdriver 0.3.2
 
 ### bug fixes
-* Changed the constraint on filesystrem-trees to avoid a broken version
+* Changed the constraint on filesystrem-trees to avoid a broken version.
 * Added the missing exports for addFile and deleteFile in Common.Profile and Firefox.Profile
 
 ### new features
@@ -403,11 +421,11 @@
 ### API changes
 * FailedCommandInfo changed so that it stores a WDSession rather than just a Maybe SessionId, thus providing server host and port information as well as the session ID.
 * As a result, mkFailedCommandInfo is now String -> WD FailedCommandInfo, since it requires access to the WDSession state.
-* HTML5StorageType changed to the more accurate WebStorageType
+* HTML5StorageType changed to the more accurate WebStorageType.
 
 ### new features
-* general documentation improvements
-* the uploadFile, uploadRawFile, and uploadZipEntry functions, which support uploading file contents to the remote server
+* general documentation improvements.
+* the uploadFile, uploadRawFile, and uploadZipEntry functions, which support uploading file contents to the remote server.
 
 ## hs-webdriver 0.1
 
@@ -415,8 +433,8 @@
 * getWindowSize, setWindowSize, getWindowPos, and setWindowPos have all been deprived of their WindowHandle argument. This is due to the fact that using unfocused windows with those commands causes undefined behavior.
 
 ### new features
-* the mkCookie function for convenient cookie construction
-* the focusFrame function for focusing to individual frames
-* the setPageLoadTimeout function
-* the maximize function for maximizing windows
-* support for HTML 5 web storage
+* the mkCookie function for convenient cookie construction.
+* the focusFrame function for focusing to individual frames.
+* the setPageLoadTimeout function.
+* the maximize function for maximizing windows.
+* support for HTML 5 web storage.
diff --git a/app/Main.hs b/app/Main.hs
new file mode 100644
--- /dev/null
+++ b/app/Main.hs
@@ -0,0 +1,43 @@
+{-# LANGUAGE NumericUnderscores #-}
+
+module Main where
+
+import Control.Monad.IO.Class
+import Control.Monad.Logger
+import Test.WebDriver
+import Test.WebDriver.Capabilities
+import Test.WebDriver.WD
+import UnliftIO.Concurrent (threadDelay)
+import UnliftIO.Directory
+import UnliftIO.Exception
+
+
+main :: IO ()
+main = do
+  -- You must have a compatible chrome/chromedriver on your PATH for this to work
+  Just chromedriverBin <- findExecutable "chromedriver"
+  Just chromeBin <- findExecutable "google-chrome-stable"
+
+  let caps = defaultCaps {
+        _capabilitiesGoogChromeOptions = Just $ defaultChromeOptions {
+            _chromeOptionsBinary = Just chromeBin
+            }
+        }
+
+  let driverConfig = DriverConfigChromedriver {
+        driverConfigChromedriver = chromedriverBin
+        , driverConfigChromedriverFlags = []
+        , driverConfigChrome = chromeBin
+        , driverConfigLogDir = Nothing
+        }
+
+  runStdoutLoggingT $ bracket mkEmptyWebDriverContext teardownWebDriverContext $ \wdc -> do
+    sess <- startSession wdc driverConfig caps "session1"
+    runWD sess $ do
+      openPage "http://www.xkcd.com"
+      liftIO $ threadDelay 5_000_000
+
+    sess2 <- startSession wdc driverConfig caps "session2"
+    runWD sess2 $ do
+      openPage "http://www.smbc-comics.com"
+      liftIO $ threadDelay 5_000_000
diff --git a/src/Test/WebDriver.hs b/src/Test/WebDriver.hs
--- a/src/Test/WebDriver.hs
+++ b/src/Test/WebDriver.hs
@@ -27,6 +27,10 @@
 <https://github.com/mozilla/geckodriver/issues/1946>). So, this library will spin
 up a separate @geckodriver@ process for every session.
 
+For an example of using this package by itself, see "Test.WebDriver.WD". For a
+full test framework integrated with this package, see
+[sandwich-webdriver](https://hackage.haskell.org/package/sandwich-webdriver).
+
 -}
 
 module Test.WebDriver (
@@ -172,8 +176,9 @@
 -- not shut driver processes.
 closeSession' :: (WebDriverBase m, MonadLogger m) => Session -> m ()
 closeSession' (Session { sessionId=(SessionId sessId), .. }) = do
-  response <- doCommandBase sessionDriver methodDelete ("/session/" <> sessId) Null
-  logInfoN [i|Close result: #{response}|]
+  _response <- doCommandBase sessionDriver methodDelete ("/session/" <> sessId) Null
+  -- TODO: throw an exception if this failed?
+  return ()
 
 -- | Create a manual 'Driver' to use with 'startSession''/'closeSession''.
 mkManualDriver :: MonadIO m =>
@@ -197,15 +202,15 @@
     , _driverManager = manager
     , _driverProcess = Nothing
     , _driverLogAsync = Nothing
-    , _driverConfig = DriverConfigChromedriver "" [] "" "" -- Not used
+    , _driverConfig = DriverConfigChromedriver "" [] "" Nothing -- Not used
     }
 
 -- | Tear down all sessions and processes associated with a 'WebDriverContext'.
 teardownWebDriverContext :: (WebDriverBase m, MonadLogger m) => WebDriverContext -> m ()
-teardownWebDriverContext wdc@(WebDriverContext {..}) = do
+teardownWebDriverContext (WebDriverContext {..}) = do
   modifyMVar_ _webDriverSessions $ \sessions -> do
     forM_ [sess | (_, sess) <- M.toList sessions] $ \sess ->
-      closeSession wdc sess
+      closeSession' sess
     return mempty
 
   modifyMVar_ _webDriverSelenium $ \case
diff --git a/src/Test/WebDriver/LaunchDriver.hs b/src/Test/WebDriver/LaunchDriver.hs
--- a/src/Test/WebDriver/LaunchDriver.hs
+++ b/src/Test/WebDriver/LaunchDriver.hs
@@ -63,27 +63,34 @@
 
   let hostname = "localhost"
 
-  (logFilePath, logFileHandle) <- liftIO $ openTempFile (driverConfigLogDir driverConfig) ((driverBaseName driverConfig) <> ".log")
-  logDebugN [i|Logging driver output to #{logFilePath}|]
+  maybeLogFileHandle <- case driverConfigLogDir driverConfig of
+    Nothing -> return Nothing
+    Just logDir -> do
+      (logFilePath, logFileHandle) <- liftIO $ openTempFile logDir ((driverBaseName driverConfig) <> ".log")
+      logDebugN [i|Logging driver output to #{logFilePath}|]
+      return $ Just logFileHandle
 
   let handler (e :: SomeException) = do
         terminateProcess p
         now <- liftIO getCurrentTime
-        liftIO (T.hPutStrLn logFileHandle [i|(#{now}) haskell-webdriver: process ending with exception: #{e}|])
-        liftIO (hClose logFileHandle)
+        whenJust maybeLogFileHandle $ \logFileHandle -> do
+          liftIO (T.hPutStrLn logFileHandle [i|(#{now}) haskell-webdriver: process ending with exception: #{e}|])
+          liftIO (hClose logFileHandle)
 
   flip withException handler $ do
     -- Read from the (combined) output stream until we see the up and running message
     maybeReady <- timeout 30_000_000 $ fix $ \loop -> do
       line <- fmap T.pack $ liftIO $ hGetLine hRead
-      liftIO $ T.hPutStrLn logFileHandle line
+      whenJust maybeLogFileHandle $ \logFileHandle ->
+        liftIO $ T.hPutStrLn logFileHandle line
       unless (Prelude.any (`T.isInfixOf` line) (needles driverConfig)) loop
     when (isNothing maybeReady) $
       throwIO DriverNoReadyMessage
 
-    logAsync <- async $ flip finally (liftIO $ hClose logFileHandle) $ forever $ do
+    logAsync <- async $ flip finally (liftIO $ whenJust maybeLogFileHandle hClose) $ forever $ do
       line <- liftIO (T.hGetLine hRead)
-      liftIO $ T.hPutStrLn logFileHandle line
+      whenJust maybeLogFileHandle $ \logFileHandle ->
+        liftIO $ T.hPutStrLn logFileHandle line
 
     flip onException (cancel logAsync) $ do
       -- Wait for a successful connection to the server socket
@@ -176,12 +183,12 @@
 
 getSubDriverArgs :: Monad m => DriverConfig -> m [FilePath]
 getSubDriverArgs (DriverConfigChromedriver {..}) = do
-  let chromedriverLog = driverConfigLogDir </> "chromedriver.log"
-  return [
-    "-Dwebdriver.chrome.logfile=" <> chromedriverLog
-    , "-Dwebdriver.chrome.verboseLogging=true"
-    , "-Dwebdriver.chrome.driver=" <> driverConfigChromedriver
-    ]
+  let logArgs = case driverConfigLogDir of
+        Just d -> ["-Dwebdriver.chrome.logfile=" <> (d </> "chromedriver.log")
+                  , "-Dwebdriver.chrome.verboseLogging=true"
+                  ]
+        Nothing -> []
+  return (["-Dwebdriver.chrome.driver=" <> driverConfigChromedriver] <> logArgs)
 getSubDriverArgs (DriverConfigGeckodriver {..}) = do
   return [
     "-Dwebdriver.gecko.driver=" <> driverConfigGeckodriver
@@ -238,3 +245,6 @@
   case _driverLogAsync of
     Just x -> cancel x
     Nothing -> return ()
+
+whenJust :: Applicative m => Maybe a -> (a -> m ()) -> m ()
+whenJust mg f = maybe (pure ()) f mg
diff --git a/src/Test/WebDriver/Types.hs b/src/Test/WebDriver/Types.hs
--- a/src/Test/WebDriver/Types.hs
+++ b/src/Test/WebDriver/Types.hs
@@ -105,7 +105,7 @@
     -- | Drivers to configure Selenium to use.
     , driverConfigSubDrivers :: [DriverConfig]
     -- | Directory in which to place driver logs.
-    , driverConfigLogDir :: FilePath
+    , driverConfigLogDir :: Maybe FilePath
     }
   | DriverConfigGeckodriver {
       -- | Path to @geckodriver@ binary.
@@ -115,7 +115,7 @@
       -- | Path to @firefox@ binary.
       , driverConfigFirefox :: FilePath
       -- | Directory in which to place driver logs.
-      , driverConfigLogDir :: FilePath
+      , driverConfigLogDir :: Maybe FilePath
       }
   | DriverConfigChromedriver {
       -- | Path to @chromedriver@ binary.
@@ -125,7 +125,7 @@
       -- | Path to @chrome@ binary.
       , driverConfigChrome :: FilePath
       -- | Directory in which to place driver logs.
-      , driverConfigLogDir :: FilePath
+      , driverConfigLogDir :: Maybe FilePath
       }
 
 -- | An opaque identifier for a WebDriver session. These handles are produced by
diff --git a/src/Test/WebDriver/WD.hs b/src/Test/WebDriver/WD.hs
new file mode 100644
--- /dev/null
+++ b/src/Test/WebDriver/WD.hs
@@ -0,0 +1,102 @@
+{-# LANGUAGE CPP #-}
+{-# LANGUAGE GeneralizedNewtypeDeriving #-}
+{-# LANGUAGE MultiWayIf #-}
+{-# LANGUAGE NumericUnderscores #-}
+{-# LANGUAGE ViewPatterns #-}
+{-# OPTIONS_GHC -fno-warn-orphans #-}
+
+{-|
+
+This module contains the 'WD' monad, which serves as an example of how to use
+this library in a standalone fashion.
+
+For more complex usage, you'll probably want to skip this module and write
+'WebDriverBase' and 'SessionState' instances for your own monads.
+
+You can find a full example in the project repo at
+<https://github.com/haskell-webdriver/haskell-webdriver/blob/main/app/Main.hs>.
+
+-}
+
+module Test.WebDriver.WD (
+  WD(..)
+  , runWD
+  ) where
+
+import Control.Monad.Catch (MonadCatch, MonadMask, MonadThrow)
+import Control.Monad.IO.Class
+import Control.Monad.IO.Unlift
+import Control.Monad.Logger
+import Control.Monad.Reader
+import qualified Data.Aeson as A
+import qualified Data.ByteString as B
+import qualified Data.ByteString.Lazy as BL
+import Data.String.Interpolate
+import qualified Data.Text as T
+import qualified Network.HTTP.Client as HC
+import Network.HTTP.Types.Status as N
+import Test.WebDriver
+import Test.WebDriver.Types
+import UnliftIO.Exception
+
+#if MIN_VERSION_aeson(2,0,0)
+import qualified Data.Aeson.Key as A
+import qualified Data.Aeson.KeyMap as KM
+
+aesonLookup :: T.Text -> KM.KeyMap v -> Maybe v
+aesonLookup = KM.lookup . A.fromText
+#else
+import qualified Data.HashMap.Strict as HM
+
+aesonLookup :: (Eq k, Hashable k) => k -> HM.HashMap k v -> Maybe v
+aesonLookup = HM.lookup
+#endif
+
+newtype WD a = WD (ReaderT Session (LoggingT IO) a)
+  deriving (Functor, Applicative, Monad, MonadIO, MonadThrow, MonadCatch, MonadMask, MonadUnliftIO, MonadReader Session, MonadLogger)
+
+doCommandBaseWithLogging :: (
+  MonadLogger m, MonadUnliftIO m, A.ToJSON p
+  ) => Driver -> Method -> T.Text -> p -> m (HC.Response BL.ByteString)
+doCommandBaseWithLogging driver method path args = do
+  let req = mkDriverRequest driver method path args
+  logDebugN [i|--> #{HC.method req} #{HC.path req}#{HC.queryString req} (#{showRequestBody (HC.requestBody req)})|]
+  response <- tryAny (liftIO $ HC.httpLbs req (_driverManager driver)) >>= either throwIO return
+  let (N.Status code _) = HC.responseStatus response
+
+  if | code >= 200 && code < 300 -> case A.eitherDecode (HC.responseBody response) of
+         -- For successful responses, try to pull out the "value" and show it
+         Right (A.Object (aesonLookup "value" -> Just value)) -> logDebugN [i|<-- #{code} #{A.encode value}|]
+         _ -> logDebugN [i|<-- #{code} #{HC.responseBody response}|]
+     -- For non-successful responses, log the entire response.
+     -- Reading the WebDriver spec, it would probably be sufficient to just show the "value" as above,
+     -- plus the HTTP status message.
+     | otherwise -> logDebugN [i|<-- #{code} #{response}|]
+  return response
+
+  where
+    showRequestBody :: HC.RequestBody -> B.ByteString
+    showRequestBody (HC.RequestBodyLBS bytes) = BL.toStrict bytes
+    showRequestBody (HC.RequestBodyBS bytes) = bytes
+    showRequestBody _ = "<request body>"
+
+instance WebDriverBase WD where
+  doCommandBase = doCommandBaseWithLogging
+
+instance WebDriverBase (LoggingT IO) where
+  doCommandBase = doCommandBaseWithLogging
+
+instance SessionState WD where
+  getSession = ask
+
+-- The following is a more general SessionState for MonadReader monads, but it
+-- requires UndecidableInstances
+-- class HasSession a where
+--   extractSession :: a -> Session
+-- instance HasSession Session where
+--   extractSession = id
+-- instance (HasSession a, MonadReader a m) => SessionState m where
+--   getSession = asks extractSession
+
+runWD :: Session -> WD a -> LoggingT IO a
+runWD sess (WD wd) = runReaderT wd sess
diff --git a/tests/Main.hs b/tests/Main.hs
--- a/tests/Main.hs
+++ b/tests/Main.hs
@@ -45,7 +45,7 @@
               driverConfigJava = java
               , driverConfigSeleniumJar = seleniumJar
               , driverConfigSubDrivers = subDrivers
-              , driverConfigLogDir = dir
+              , driverConfigLogDir = Just dir
               , driverConfigJavaFlags = []
               , driverConfigSeleniumVersion = Just Selenium3
               }
@@ -62,7 +62,7 @@
               driverConfigJava = java
               , driverConfigSeleniumJar = seleniumJar
               , driverConfigSubDrivers = subDrivers
-              , driverConfigLogDir = dir
+              , driverConfigLogDir = Just dir
               , driverConfigJavaFlags = []
               , driverConfigSeleniumVersion = Just Selenium4
               }
@@ -77,7 +77,7 @@
             return $ DriverConfigChromedriver {
               driverConfigChromedriver = chromedriver
               , driverConfigChrome = chrome
-              , driverConfigLogDir = dir
+              , driverConfigLogDir = Just dir
               , driverConfigChromedriverFlags = []
               }
 
@@ -91,7 +91,7 @@
             return $ DriverConfigGeckodriver {
               driverConfigGeckodriver = geckodriver
               , driverConfigFirefox = firefox
-              , driverConfigLogDir = dir
+              , driverConfigLogDir = Just dir
               , driverConfigGeckodriverFlags = []
               }
 
@@ -134,14 +134,14 @@
     DriverConfigChromedriver {
         driverConfigChromedriver = browserDependenciesChromeChromedriver
         , driverConfigChrome = browserDependenciesChromeChrome
-        , driverConfigLogDir = dir
+        , driverConfigLogDir = Just dir
         , driverConfigChromedriverFlags = []
         }]
   BrowserDependenciesFirefox {..} -> return [
     DriverConfigGeckodriver {
         driverConfigGeckodriver = browserDependenciesFirefoxGeckodriver
         , driverConfigFirefox = browserDependenciesFirefoxFirefox
-        , driverConfigLogDir = dir
+        , driverConfigLogDir = Just dir
         , driverConfigGeckodriverFlags = []
     }]
 
diff --git a/webdriver.cabal b/webdriver.cabal
--- a/webdriver.cabal
+++ b/webdriver.cabal
@@ -5,7 +5,7 @@
 -- see: https://github.com/sol/hpack
 
 name:           webdriver
-version:        0.13.0.0
+version:        0.14.0.0
 synopsis:       a Haskell client for the Selenium WebDriver protocol
 description:    A WebDriver client for Haskell.
                 You can use it to automate browser sessions for testing, system administration, etc.
@@ -15,6 +15,8 @@
                 * <https://www.selenium.dev/>
                 * <https://github.com/mozilla/geckodriver>
                 * <https://developer.chrome.com/docs/chromedriver>
+                .
+                See the announcement blog post [here](https://thomasjm.github.io/posts/webdriver-13) to learn about the changes in @0.13.0.0@.
 category:       Web, Browser, Testing, WebDriver, Selenium
 homepage:       https://github.com/haskell-webdriver/haskell-webdriver#readme
 bug-reports:    https://github.com/haskell-webdriver/haskell-webdriver/issues
@@ -48,6 +50,7 @@
       Test.WebDriver.Profile
       Test.WebDriver.Types
       Test.WebDriver.Waits
+      Test.WebDriver.WD
   other-modules:
       Test.WebDriver.Capabilities.Aeson
       Test.WebDriver.Capabilities.ChromeOptions
@@ -112,6 +115,7 @@
     , http-types >=0.8
     , microlens-th >=0.4.0.0
     , monad-logger
+    , mtl
     , network
     , network-uri >=2.6
     , random
@@ -127,6 +131,38 @@
     , unordered-containers >=0.1.3
     , websockets >=0.13
     , zip-archive >=0.1.1.8
+  default-language: Haskell2010
+
+executable demo
+  main-is: Main.hs
+  other-modules:
+      Paths_webdriver
+  hs-source-dirs:
+      app
+  default-extensions:
+      FlexibleContexts
+      FlexibleInstances
+      LambdaCase
+      NamedFieldPuns
+      OverloadedStrings
+      QuasiQuotes
+      RecordWildCards
+      ScopedTypeVariables
+  ghc-options: -threaded -rtsopts -with-rtsopts=-N -O2 -W
+  build-depends:
+      aeson
+    , base
+    , bytestring
+    , exceptions
+    , http-client
+    , http-types
+    , monad-logger
+    , mtl
+    , string-interpolate
+    , text
+    , unliftio
+    , unliftio-core
+    , webdriver
   default-language: Haskell2010
 
 test-suite tests
