webdriver 0.13.0.0 → 0.14.0.0
raw patch · 8 files changed
+334/−120 lines, 8 filesdep ~aesondep ~basedep ~bytestringnew-component:exe:demo
Dependency ranges changed: aeson, base, bytestring, exceptions, http-client, http-types, text
Files
- CHANGELOG.md +110/−92
- app/Main.hs +43/−0
- src/Test/WebDriver.hs +10/−5
- src/Test/WebDriver/LaunchDriver.hs +23/−13
- src/Test/WebDriver/Types.hs +3/−3
- src/Test/WebDriver/WD.hs +102/−0
- tests/Main.hs +6/−6
- webdriver.cabal +37/−1
CHANGELOG.md view
@@ -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.
+ app/Main.hs view
@@ -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
src/Test/WebDriver.hs view
@@ -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
src/Test/WebDriver/LaunchDriver.hs view
@@ -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
src/Test/WebDriver/Types.hs view
@@ -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
+ src/Test/WebDriver/WD.hs view
@@ -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
tests/Main.hs view
@@ -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 = [] }]
webdriver.cabal view
@@ -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