diff --git a/CHANGELOG.md b/CHANGELOG.md
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -1,27 +1,43 @@
-# Change Log
+#Change Log
 
-## hs-webdriver 0.3.3
+##hs-webdriver 0.4
 
+###API changes
+* finallyClose and closeOnException are now overloaded on the WebDriver class.
+* NoSessionId and doSessCommand were moved from Test.WebDriver.Classes to Test.WebDriver.Commands.Internal
+
 ###bug fixes
-*The default preferences used by Selenium are now merged into the preferences of Firefox profiles loaded from disk.
-*addExtension will now correctly add extension directories to a profile.
+* 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
 
+###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.
+
+## hs-webdriver 0.3.3
+
 ###API changes
 * The representation of profile files has been changed to use a HashMap instead of an association list. This ensures that destination paths are always unique.
 
+###bug fixes
+* The default preferences used by Selenium are now merged into the preferences of Firefox profiles loaded from disk.
+* addExtension will now correctly add extension directories to a profile.
+
+###known issues
+* Because of the way loadProfile currently adds directories to the profileFiles HashMap, it's possible for extensions added via addExtension to be overriden by the extensions originally listed in the on-disk extensions directory.
+
 ###new features 
-*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
+* 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
 
 
 ## 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
 
@@ -43,7 +59,7 @@
 * loadProfile now properly loads an entire Firefox profile from disk, rather than just the extensions and preferences.
 
 ###known issues
-*An issue involving lazy bytestring IO in the zip-archive package means that unusually large profiles might exceed the OSes open file limit.
+* An issue involving lazy bytestring IO in the zip-archive package means that unusually large profiles might exceed the OSes open file limit.
 
 ###new features
 * several new functions for working with Firefox/Opera profiles have been added. This includes functions for loading large profiles from disk, functions for working with zipped profiles, and functions for adding arbitrary files to a profile in pure code. 
diff --git a/TODO b/TODO
--- a/TODO
+++ b/TODO
@@ -6,7 +6,8 @@
 
 -- add support for Opera profiles
 
--- improve documentation. Document errors.
+-- improve documentation
+  -- document errors.
   -- provide examples
     -- basic runSession usage
     -- intermediate usage
@@ -33,3 +34,5 @@
 misc. unsorted
 
 --Use lazy ByteString where possible. patch base64-bytestring to work with lazy bytestrings
+
+--consider adding withSession to SessionState so that it can be overloaded easily, or rewriting it so that it's overloaded but not a method itself.
diff --git a/src/Test/WebDriver.hs b/src/Test/WebDriver.hs
--- a/src/Test/WebDriver.hs
+++ b/src/Test/WebDriver.hs
@@ -7,14 +7,14 @@
          WD(..), WDSession(..), defaultSession, SessionId(..)
          -- * Running WebDriver tests
        , runWD, runSession, withSession, finallyClose, closeOnException
+         -- * WebDriver commands
+       , module Test.WebDriver.Commands
          -- * Capabilities and configuration
        , Capabilities(..), defaultCaps, allCaps
        , Platform(..), ProxyType(..)
          -- ** Browser-specific configuration
        , Browser(..), LogPref(..)
        , firefox, chrome, ie, opera, iPhone, iPad, android
-         -- * WebDriver commands
-       , module Test.WebDriver.Commands
          -- * Exceptions
        , module Test.WebDriver.Exceptions
        ) where
diff --git a/src/Test/WebDriver/Classes.hs b/src/Test/WebDriver/Classes.hs
--- a/src/Test/WebDriver/Classes.hs
+++ b/src/Test/WebDriver/Classes.hs
@@ -7,23 +7,15 @@
          SessionState(..), modifySession
          -- ** WebDriver sessions
        , WDSession(..), SessionId(..), defaultSession
-         -- **Convenience function for :sessionId URLs
-       , doSessCommand
-         -- * No Session Exception
-       , NoSessionId(..)
        ) where
 
 --import Test.WebDriver.Internal
 import Data.Aeson
 import Network.HTTP (RequestMethod(..))
 
-import qualified Data.Text as T
 import Data.Text (Text)
 
 import Control.Monad.Trans.Control
-import Control.Exception.Lifted (Exception, throwIO)
-import Data.Typeable
-
 import Control.Monad.Trans.Maybe
 import Control.Monad.Trans.Identity
 import Control.Monad.List
@@ -102,29 +94,6 @@
 newtype SessionId = SessionId Text
                   deriving (Eq, Ord, Show, Read, 
                             FromJSON, ToJSON)
-
-
-instance Exception NoSessionId
--- |A command requiring a session ID was attempted when no session ID was 
--- available.
-newtype NoSessionId = NoSessionId String 
-                 deriving (Eq, Show, Typeable)
-
-
--- |This a convenient wrapper around 'doCommand' that automatically prepends
--- the session URL parameter to the wire command URL. For example, passing
--- a URL of "/refresh" will expand to "/session/:sessionId/refresh".
-doSessCommand :: (WebDriver wd, ToJSON a, FromJSON b) => 
-                  RequestMethod -> Text -> a -> wd b
-doSessCommand method path args = do
-  WDSession { wdSessId = mSessId } <- getSession
-  case mSessId of 
-      Nothing -> throwIO . NoSessionId $ msg
-        where 
-          msg = "doSessCommand: No session ID found for relative URL "
-                ++ show path
-      Just (SessionId sId) -> doCommand method 
-                              (T.concat ["/session/", sId, path]) args
 
 
 instance SessionState m => SessionState (LS.StateT s m) where
diff --git a/src/Test/WebDriver/Commands.hs b/src/Test/WebDriver/Commands.hs
--- a/src/Test/WebDriver/Commands.hs
+++ b/src/Test/WebDriver/Commands.hs
@@ -1,5 +1,5 @@
 {-# LANGUAGE OverloadedStrings, ScopedTypeVariables, ExistentialQuantification,
-             GeneralizedNewtypeDeriving, TemplateHaskell #-}
+             TemplateHaskell #-}
 -- |This module exports basic WD actions that can be used to interact with a
 -- browser session.
 module Test.WebDriver.Commands 
@@ -18,6 +18,7 @@
        , findElem, findElems, findElemFrom, findElemsFrom
          -- ** Interacting with elements
        , click, submit, getText
+         -- *** Sending key inputs to elements
        , sendKeys, sendRawKeys, clearInput
          -- ** Element information
        , attr, cssProp, elemPos, elemSize
@@ -70,6 +71,7 @@
        , serverStatus
        ) where
 
+import Test.WebDriver.Commands.Internal
 import Test.WebDriver.Classes
 import Test.WebDriver.JSON
 import Test.WebDriver.Capabilities
@@ -93,7 +95,6 @@
 import Control.Exception.Lifted (throwIO, catch, handle)
 import Data.Word
 import Data.String (fromString)
-import Data.Default
 import qualified Data.Char as C
 
 import Prelude hiding (catch)
@@ -112,7 +113,7 @@
   sessUrl <- doCommand POST "/session" . single "desiredCapabilities" $ caps
   let sessId = SessionId . last . filter (not . T.null) . splitOn "/" $  sessUrl
   modifySession $ \sess -> sess {wdSessId = Just sessId}
-  return =<< getSession
+  getSession
 
 -- |Retrieve a list of active sessions and their 'Capabilities'.
 sessions :: WebDriver wd => wd [(SessionId, Capabilities)]
@@ -263,18 +264,6 @@
 focusFrame :: WebDriver wd => FrameSelector -> wd ()
 focusFrame s = doSessCommand POST "/frame" . single "id" $ s 
 
-{- |An opaque identifier for a browser window -}
-newtype WindowHandle = WindowHandle Text
-                     deriving (Eq, Ord, Show, Read, 
-                               FromJSON, ToJSON)
-instance Default WindowHandle where
-  def = currentWindow
-
--- |A special 'WindowHandle' that always refers to the currently focused window.
--- This is also used by the 'Default' instance.
-currentWindow :: WindowHandle
-currentWindow = WindowHandle "current"
-
 -- |Returns a handle to the currently focused window
 getCurrentWindow :: WebDriver wd => wd WindowHandle
 getCurrentWindow = doSessCommand GET "/window_handle" ()
@@ -313,11 +302,6 @@
 setWindowPos :: WebDriver wd => (Int, Int) -> wd ()
 setWindowPos = doWinCommand POST currentWindow "/position" . pair ("x","y")
 
-doWinCommand :: (WebDriver wd, ToJSON a, FromJSON b) => 
-                 RequestMethod -> WindowHandle -> Text -> a -> wd b
-doWinCommand m (WindowHandle w) path a = 
-  doSessCommand m (T.concat ["/window/", w, path]) a
-
 -- |Cookies are delicious delicacies. When sending cookies to the server, a value
 -- of Nothing indicates that the server should use a default value. When receiving
 -- cookies from the server, a value of Nothing indicates that the server is unable
@@ -389,17 +373,6 @@
 getTitle :: WebDriver wd => wd Text
 getTitle = doSessCommand GET "/title" ()
 
-{- |An opaque identifier for a web page element. -}
-newtype Element = Element Text
-                  deriving (Eq, Ord, Show, Read)
-
-instance FromJSON Element where
-  parseJSON (Object o) = Element <$> o .: "ELEMENT"
-  parseJSON v = typeMismatch "Element" v
-
-instance ToJSON Element where
-  toJSON (Element e) = object ["ELEMENT" .= e]
-
 -- |Specifies element(s) within a DOM tree using various selection methods.
 data Selector = ById Text  
               | ByName Text
@@ -522,11 +495,6 @@
 (</=>) :: WebDriver wd => Element -> Element -> wd Bool
 e1 </=> e2 = not <$> (e1 <==> e2)
 
-doElemCommand :: (WebDriver wd, ToJSON a, FromJSON b) => 
-                  RequestMethod -> Element -> Text -> a -> wd b
-doElemCommand m (Element e) path a =
-  doSessCommand m (T.concat ["/element/", e, path]) a
-
 -- |A screen orientation
 data Orientation = Landscape | Portrait
                  deriving (Eq, Show, Ord, Bounded, Enum)
@@ -585,14 +553,16 @@
                  deriving (Eq, Show, Ord, Bounded, Enum)
 
 instance ToJSON MouseButton where
-  toJSON = String . toUpper . fromString . show
+  toJSON = toJSON . fromEnum
   
 instance FromJSON MouseButton where
-  parseJSON (String jStr) = case toLower jStr of
-    "left"   -> return LeftButton
-    "middle" -> return MiddleButton
-    "right"  -> return RightButton
-    err      -> fail $ "Invalid MouseButton string " ++ show err
+  parseJSON v = do
+    n <- parseJSON v
+    case n :: Integer of
+      0 -> return LeftButton
+      1 -> return MiddleButton
+      2 -> return RightButton
+      err -> fail $ "Invalid JSON for MouseButton: " ++ show err
   parseJSON v = typeMismatch "MouseButton" v
 
 -- |Click at the current mouse position with the given mouse button.
@@ -710,10 +680,6 @@
                  . fromArchive . (`addEntryToArchive` emptyArchive)
 
 
--- |An HTML 5 storage type
-data WebStorageType = LocalStorage | SessionStorage 
-                    deriving (Eq, Show, Ord, Bounded, Enum)
-
 -- |Get the current number of keys in a web storage area.
 storageSize :: WebDriver wd => WebStorageType -> wd Integer
 storageSize s = doStorageCommand GET s "/size" ()
@@ -726,6 +692,10 @@
 deleteAllKeys :: WebDriver wd => WebStorageType -> wd ()
 deleteAllKeys s = doStorageCommand DELETE s "" ()
 
+-- |An HTML 5 storage type
+data WebStorageType = LocalStorage | SessionStorage 
+                    deriving (Eq, Show, Ord, Bounded, Enum)
+          
 -- |Get the value associated with a key in the given web storage area.
 -- Unset keys result in empty strings, since the Web Storage spec
 -- makes no distinction between the empty string and an undefined value.
@@ -740,11 +710,15 @@
 deleteKey :: WebDriver wd => WebStorageType -> Text -> wd ()
 deleteKey s k = doStorageCommand POST s ("/key/" `T.append` k) ()
 
+-- |A wrapper around 'doStorageCommand' to create web storage URLs.
 doStorageCommand :: (WebDriver wd, ToJSON a, FromJSON b) =>
                      RequestMethod -> WebStorageType -> Text -> a -> wd b
 doStorageCommand m s path a = doSessCommand m (T.concat ["/", s', path]) a
   where s' = case s of
           LocalStorage -> "local_storage"
           SessionStorage -> "session_storage"
-          
+
+
+-- Moving this closer to the definition of Cookie seems to cause strange compile 
+-- errors, so I'm leaving it here for now.
 $( deriveToJSON (map C.toLower . drop 4) ''Cookie )
diff --git a/src/Test/WebDriver/Commands/Internal.hs b/src/Test/WebDriver/Commands/Internal.hs
new file mode 100644
--- /dev/null
+++ b/src/Test/WebDriver/Commands/Internal.hs
@@ -0,0 +1,93 @@
+{-# LANGUAGE OverloadedStrings, DeriveDataTypeable, GeneralizedNewtypeDeriving  #-}
+{-# OPTIONS_HADDOCK not-home #-}
+-- |Internal functions used to implement the functions exported by 
+-- "Test.WebDriver.Commands". These may be useful for implementing non-standard 
+-- webdriver commands.
+module Test.WebDriver.Commands.Internal 
+       (-- * Low-level webdriver functions
+         doCommand
+        -- ** Commands with :sessionId URL parameter
+       , doSessCommand, SessionId(..)
+        -- ** Commands with element :id URL parameters
+       , doElemCommand, Element(..)
+        -- ** Commands with :windowHandle URL parameters
+       , doWinCommand, WindowHandle(..), currentWindow
+        -- * Exceptions
+       , NoSessionId(..)
+       ) where
+
+import Test.WebDriver.Classes
+
+import Data.Aeson
+import Data.Aeson.Types
+import Data.Text (Text)
+import qualified Data.Text as T
+import Control.Exception.Lifted
+import Data.Typeable
+import Data.Default
+import Control.Applicative
+
+{- |An opaque identifier for a web page element. -}
+newtype Element = Element Text
+                  deriving (Eq, Ord, Show, Read)
+
+instance FromJSON Element where
+  parseJSON (Object o) = Element <$> o .: "ELEMENT"
+  parseJSON v = typeMismatch "Element" v
+
+instance ToJSON Element where
+  toJSON (Element e) = object ["ELEMENT" .= e]
+
+
+{- |An opaque identifier for a browser window -}
+newtype WindowHandle = WindowHandle Text
+                     deriving (Eq, Ord, Show, Read, 
+                               FromJSON, ToJSON)
+instance Default WindowHandle where
+  def = currentWindow
+
+-- |A special 'WindowHandle' that always refers to the currently focused window.
+-- This is also used by the 'Default' instance.
+currentWindow :: WindowHandle
+currentWindow = WindowHandle "current"
+
+instance Exception NoSessionId
+-- |A command requiring a session ID was attempted when no session ID was 
+-- available.
+newtype NoSessionId = NoSessionId String 
+                 deriving (Eq, Show, Typeable)
+
+-- |This a convenient wrapper around 'doCommand' that automatically prepends
+-- the session URL parameter to the wire command URL. For example, passing
+-- a URL of \"/refresh\" will expand to \"/session/:sessionId/refresh\", where
+-- :sessionId is a URL parameter as described in 
+-- <http://code.google.com/p/selenium/wiki/JsonWireProtocol>
+doSessCommand :: (WebDriver wd, ToJSON a, FromJSON b) => 
+                  RequestMethod -> Text -> a -> wd b
+doSessCommand method path args = do
+  WDSession { wdSessId = mSessId } <- getSession
+  case mSessId of 
+      Nothing -> throwIO . NoSessionId $ msg
+        where 
+          msg = "doSessCommand: No session ID found for relative URL "
+                ++ show path
+      Just (SessionId sId) -> doCommand method 
+                              (T.concat ["/session/", sId, path]) args
+
+-- |A wrapper around 'doSessCommand' to create element URLs.
+-- For example, passing a URL of "/active" will expand to 
+-- \"/session/:sessionId/element/:id/active\", where :sessionId and :id are URL
+-- parameters as described in the wire protocol.
+doElemCommand :: (WebDriver wd, ToJSON a, FromJSON b) => 
+                  RequestMethod -> Element -> Text -> a -> wd b
+doElemCommand m (Element e) path a =
+  doSessCommand m (T.concat ["/element/", e, path]) a
+
+-- |A wrapper around 'doSessCommand' to create window handle URLS.
+-- For example, passing a URL of \"/size\" will expand to
+-- \"/session/:sessionId/window/:windowHandle/\", where :sessionId and 
+-- :windowHandle are URL parameters as described in the wire protocol
+doWinCommand :: (WebDriver wd, ToJSON a, FromJSON b) => 
+                 RequestMethod -> WindowHandle -> Text -> a -> wd b
+doWinCommand m (WindowHandle w) path a = 
+  doSessCommand m (T.concat ["/window/", w, path]) a
diff --git a/src/Test/WebDriver/Commands/Wait.hs b/src/Test/WebDriver/Commands/Wait.hs
--- a/src/Test/WebDriver/Commands/Wait.hs
+++ b/src/Test/WebDriver/Commands/Wait.hs
@@ -49,7 +49,7 @@
 expectAll p xs = expect . and =<< mapM p xs
 
 -- |Wait until either the given action succeeds or the timeout is reached.
--- The action will be retried every .25 seconds until no 'ExpectFailed' or
+-- The action will be retried every .5 seconds until no 'ExpectFailed' or
 -- 'FailedCommand' 'NoSuchElement' exceptions occur. If the timeout is reached, 
 -- then a 'Timeout' exception will be raised. The timeout value 
 -- is expressed in seconds.
diff --git a/src/Test/WebDriver/Exceptions.hs b/src/Test/WebDriver/Exceptions.hs
--- a/src/Test/WebDriver/Exceptions.hs
+++ b/src/Test/WebDriver/Exceptions.hs
@@ -7,5 +7,5 @@
        , mkFailedCommandInfo, failedCommand
        )where
 import Test.WebDriver.Internal
-import Test.WebDriver.Classes
 import Test.WebDriver.JSON
+import Test.WebDriver.Commands.Internal
diff --git a/src/Test/WebDriver/Firefox/Profile.hs b/src/Test/WebDriver/Firefox/Profile.hs
--- a/src/Test/WebDriver/Firefox/Profile.hs
+++ b/src/Test/WebDriver/Firefox/Profile.hs
@@ -14,7 +14,7 @@
          -- * Extensions
        , addExtension, deleteExtension, hasExtension
          -- * Other files and directories
-       , addFile, removeFile, hasFile
+       , addFile, deleteFile, hasFile
          -- * Miscellaneous profile operations
        , unionProfiles, onProfileFiles, onProfilePrefs
          -- * Loading and preparing profiles
diff --git a/src/Test/WebDriver/JSON.hs b/src/Test/WebDriver/JSON.hs
--- a/src/Test/WebDriver/JSON.hs
+++ b/src/Test/WebDriver/JSON.hs
@@ -1,7 +1,10 @@
 {-# LANGUAGE OverloadedStrings, FlexibleContexts, DeriveDataTypeable #-}
 -- |A collection of convenience functions for using and parsing JSON values
--- within 'WD'. All monadic parse errors are converted to asynchronous 
+-- within 'WD'. All monadic parse errors are converted to asynchronous
 -- 'BadJSON' exceptions.
+-- 
+-- These functions are used internally to implement webdriver commands, and may
+-- be useful for implementing non-standard commands.
 module Test.WebDriver.JSON 
        ( -- * Access a JSON object key
          (!:)
diff --git a/src/Test/WebDriver/Monad.hs b/src/Test/WebDriver/Monad.hs
--- a/src/Test/WebDriver/Monad.hs
+++ b/src/Test/WebDriver/Monad.hs
@@ -65,11 +65,11 @@
 
 -- |A finalizer ensuring that the session is always closed at the end of 
 -- the given 'WD' action, regardless of any exceptions.
-finallyClose:: WD a -> WD a 
+finallyClose:: WebDriver wd => wd a -> wd a 
 finallyClose wd = closeOnException wd <* closeSession
 
 -- |A variant of 'finallyClose' that only closes the session when an 
 -- asynchronous exception is thrown, but otherwise leaves the session open
 -- if the action was successful.
-closeOnException :: WD a -> WD a
+closeOnException :: WebDriver wd => wd a -> wd a
 closeOnException wd = wd `onException` closeSession
diff --git a/webdriver.cabal b/webdriver.cabal
--- a/webdriver.cabal
+++ b/webdriver.cabal
@@ -1,5 +1,5 @@
 Name: webdriver
-Version: 0.3.3
+Version: 0.4
 Cabal-Version: >= 1.6
 License: BSD3
 License-File: LICENSE
@@ -37,21 +37,21 @@
                  , bytestring == 0.9.*
                  , text >= 0.7 && < 0.12
                  , time == 1.*
-                 , transformers >= 0.2
-                 , zip-archive >= 0.1.1.7
+                 , transformers >= 0.2 && < 0.5
+                 , zip-archive >= 0.1.1.8 && < 0.3
                  , directory == 1.*
-                 , filepath >=1.1
-                 , unordered-containers >= 0.1.3
+                 , filepath == 1.*
+                 , unordered-containers >= 0.1.3 && < 0.4
                  , attoparsec == 0.10.*
                  , monad-control == 0.3.*
-                 , transformers-base < 1.0
-                 , vector >= 0.3
+                 , transformers-base >= 0.1 && < 1.0
+                 , vector >= 0.3 && < 0.11
                  , lifted-base == 0.1.*
                  , MonadCatchIO-transformers >= 0.3 && < 0.4
                  , filesystem-trees >= 0.1.0.2 && < 0.2
-                 , data-default
-                 , temporary
-                 , base64-bytestring
+                 , data-default >= 0.2 && < 1.0
+                 , temporary >= 1.0 && < 2.0
+                 , base64-bytestring == 0.1.*
                  , cond >= 0.3 && < 0.5
 
   exposed-modules: Test.WebDriver
@@ -60,6 +60,7 @@
                    Test.WebDriver.Exceptions
                    Test.WebDriver.Commands
                    Test.WebDriver.Commands.Wait
+                   Test.WebDriver.Commands.Internal
                    Test.WebDriver.Common.Profile
                    Test.WebDriver.Firefox.Profile
                    Test.WebDriver.Chrome.Extension
