diff --git a/packages.dhall b/packages.dhall index e455430..eddb09a 100644 --- a/packages.dhall +++ b/packages.dhall @@ -1,8 +1,8 @@ let upstream = - https://github.com/purescript/package-sets/releases/download/psc-0.13.8-20200822/packages.dhall sha256:b4f151f1af4c5cb6bf5437489f4231fbdd92792deaf32971e6bcb0047b3dd1f8 + https://github.com/purescript/package-sets/releases/download/psc-0.13.8-20200911-2/packages.dhall sha256:872c06349ed9c8210be43982dc6466c2ca7c5c441129826bcb9bf3672938f16e let overrides = {=} let additions = {=} -in upstream ⫽ overrides ⫽ additions +in upstream // overrides // additions diff --git a/spago.dhall b/spago.dhall index f36fb4e..9d97592 100644 --- a/spago.dhall +++ b/spago.dhall @@ -7,6 +7,8 @@ , "aff-promise" , "options" , "test-unit" + , "untagged-union" + , "node-buffer" ] , packages = ./packages.dhall , sources = [ "src/**/*.purs", "test/**/*.purs" ] diff --git a/src/Playwright.purs b/src/Playwright.purs index 7103020..b7d136b 100644 --- a/src/Playwright.purs +++ b/src/Playwright.purs @@ -1,180 +1,82 @@ module Playwright - ( BrowserType - , Browser - - , firefox - , chromium - , webkit - - , launch - , LaunchOptions - , headless - , executablePath - , args - , ignoreDefaultArgs - , ProxyOptions - , server - , bypass - , username - , password - , proxy - , downloadsPath - , chromiumSandbox - , firefoxUserPrefs - , handleSIGINT - , handleSIGTERM - , handleSIGHUP - , timeout - , env - , devtools - , slowMo - - , class Close + ( launch + , contexts + , isConnected + , version , close - - , class NewPage , newPage - , NewpageOptions - - , Frame - , ElementHandle - , Page - - , class Query , query , queryMany + , module Playwright.Data + , module Playwright.Options ) where import Prelude import Effect (Effect) -import Control.Promise (Promise, toAffE) -import Data.Options (Option, Options, opt, options) +import Control.Promise (toAffE) +import Data.Options (Options, options) import Effect.Aff (Aff) -import Foreign (Foreign) -import Foreign.Object (Object) +import Untagged.Union (type (|+|)) +import Node.Buffer (Buffer) +import Playwright.Data +import Playwright.Options +import Playwright.Internal (prop) -foreign import data BrowserType :: Type -foreign import data Browser :: Type +launch :: BrowserType -> Options Launch -> Aff Browser +launch bt = + options >>> + prop "launch" (\_ -> launch) bt >>> + toAffE -foreign import firefox :: BrowserType -foreign import chromium :: BrowserType -foreign import webkit :: BrowserType +close :: Browser |+| BrowserContext |+| Page -> Aff Unit +close = + prop "close" (\_ -> close) >>> toAffE -foreign import _launch :: BrowserType -> Foreign -> Effect (Promise Browser) +contexts :: Browser -> Effect (Array BrowserContext) +contexts = + prop "contexts" (\_ -> contexts) -launch :: BrowserType -> Options LaunchOptions -> Aff Browser -launch bt opts = toAffE $ _launch bt (options opts) +isConnected :: Browser -> Effect Boolean +isConnected = + prop "isConnected" (\_ -> isConnected) -foreign import data LaunchOptions :: Type +version :: Browser -> Effect String +version = + prop "version" (\_ -> version) -headless :: Option LaunchOptions Boolean -headless = opt "headless" +newPage :: Browser |+| BrowserContext -> Options Newpage -> Aff Page +newPage sth = + options >>> + prop "newPage" (\_ -> newPage) sth >>> + toAffE -executablePath :: Option LaunchOptions String -executablePath = opt "executablePath" +-- | `sth.$(selector)` +query + :: ElementHandle |+| Page |+| Frame + -> Selector + -> Aff ElementHandle +query sth = + toAffE <<< prop "$" (\_ -> query) sth -args :: Option LaunchOptions String -args = opt "args" +-- | `sth.$$(selector) +queryMany + :: ElementHandle |+| Page |+| Frame + -> Selector + -> Aff (Array ElementHandle) +queryMany sth = + toAffE <<< prop "$$" (\_ -> queryMany) sth -ignoreDefaultArgs :: Option LaunchOptions (Array String) -ignoreDefaultArgs = opt "ignoreDefaultArgs" +screenshot + :: ElementHandle |+| Page + -> Options Screenshot + -> Aff Buffer +screenshot sth = + options >>> + prop "screenshot" (\_ -> screenshot) sth >>> + toAffE -foreign import data ProxyOptions :: Type - -server :: Option ProxyOptions String -server = opt "server" - -bypass :: Option ProxyOptions String -bypass = opt "bypass" - -username :: Option ProxyOptions String -username = opt "username" - -password :: Option ProxyOptions String -password = opt "password" - -proxy :: Option LaunchOptions (Options ProxyOptions) -proxy = opt "proxy" - -downloadsPath :: Option LaunchOptions String -downloadsPath = opt "downloadsPath" - -chromiumSandbox :: Option LaunchOptions Boolean -chromiumSandbox = opt "chromiumSandbox" - -firefoxUserPrefs :: Option LaunchOptions Foreign -firefoxUserPrefs = opt "firefoxUserPrefs" - -handleSIGINT :: Option LaunchOptions Boolean -handleSIGINT = opt "handleSIGINT" - -handleSIGTERM :: Option LaunchOptions Boolean -handleSIGTERM = opt "handleSIGTERM" - -handleSIGHUP :: Option LaunchOptions Boolean -handleSIGHUP = opt "handleSIGHUP" - -timeout :: Option LaunchOptions Number -timeout = opt "timeout" - -env :: Option LaunchOptions (Object String) -env = opt "env" - -devtools :: Option LaunchOptions Boolean -devtools = opt "devtools" - -slowMo :: Option LaunchOptions Number -slowMo = opt "slowMo" - -class Close sth - -instance closeBrowser :: Close Browser - -close :: forall sth. Close sth => sth -> Aff Unit -close = toAffE <<< _close - -foreign import _close :: forall sth. sth -> Effect (Promise Unit) - -foreign import data BrowserContext :: Type - -foreign import contexts :: Browser -> Effect (Array BrowserContext) - -foreign import isConnected :: Browser -> Effect Boolean - -foreign import version :: Browser -> Effect String - -foreign import data NewpageOptions :: Type - -foreign import data Page :: Type - -class NewPage sth - -instance newpageBrowser :: NewPage Browser -instance newpageBrowserContext :: NewPage BrowserContext - -newPage :: forall sth. NewPage sth => sth -> Options NewpageOptions -> Aff Page -newPage sth opts = toAffE $ _newPage sth (options opts) - -foreign import _newPage :: forall sth. sth -> Foreign -> Effect (Promise Page) - -foreign import data ElementHandle :: Type - -class Query sth - -instance queryElementHandle :: Query ElementHandle -instance queryPage :: Query Page -instance queryFrame :: Query Frame - -type Selector = String - -foreign import data Frame :: Type - -foreign import query_ :: forall sth. sth -> Selector -> Effect (Promise ElementHandle) -foreign import queryMany_ :: forall sth. sth -> Selector -> Effect (Promise (Array ElementHandle)) - -query :: forall sth. Query sth => sth -> Selector -> Aff ElementHandle -query sth = toAffE <<< query_ sth - -queryMany :: forall sth. Query sth => sth -> Selector -> Aff (Array ElementHandle) -queryMany sth = toAffE <<< queryMany_ sth +url + :: Page |+| Frame |+| Download |+| Request |+| Response |+| Worker + -> Effect String +url = prop "url" \_ -> url diff --git a/src/Playwright/Data.js b/src/Playwright/Data.js new file mode 100644 index 0000000..0eff87c --- /dev/null +++ b/src/Playwright/Data.js @@ -0,0 +1,9 @@ +/* global require exports */ +var P = require('playwright'); + +exports.png = "png"; +exports.jpg = "jpg"; + +exports.chromium = P.chromium; +exports.firefox = P.firefox; +exports.webkit = P.webkit; diff --git a/src/Playwright/Data.purs b/src/Playwright/Data.purs new file mode 100644 index 0000000..d074647 --- /dev/null +++ b/src/Playwright/Data.purs @@ -0,0 +1,36 @@ +module Playwright.Data where + +import Prelude + +foreign import data BrowserType :: Type +foreign import data Browser :: Type +foreign import data BrowserContext :: Type +foreign import data Page :: Type +foreign import data Frame :: Type +foreign import data ElementHandle :: Type +foreign import data JSHandle :: Type +foreign import data ConsoleMessage :: Type +foreign import data Dialog :: Type +foreign import data Download :: Type +foreign import data FileChooser :: Type +foreign import data Keyboard :: Type +foreign import data Mouse :: Type +foreign import data Request :: Type +foreign import data Response :: Type +foreign import data Selectors :: Type +foreign import data Route :: Type +foreign import data Worker :: Type + +foreign import data ScreenshotType :: Type + +foreign import png :: ScreenshotType +foreign import jpg :: ScreenshotType + +newtype Selector = Selector String +derive newtype instance eqSelector :: Eq Selector +derive newtype instance showSelector :: Show Selector +derive newtype instance ordSelector :: Ord Selector + +foreign import firefox :: BrowserType +foreign import chromium :: BrowserType +foreign import webkit :: BrowserType diff --git a/src/Playwright.js b/src/Playwright/Internal.js similarity index 55% rename from src/Playwright.js rename to src/Playwright/Internal.js index 97753d7..75cc453 100644 --- a/src/Playwright.js +++ b/src/Playwright/Internal.js @@ -1,6 +1,4 @@ -/* global require exports */ - -var P = require('playwright'); +/* global exports */ /** * @param {string} property - method to call on object @@ -36,15 +34,8 @@ function effectfulGetter (property, n) { }; } -exports.chromium = P.chromium; -exports.firefox = P.firefox; -exports.webkit = P.webkit; - -exports._launch = effectfulGetter('launch', 1); -exports._close = effectfulGetter('close', 0); -exports.contexts = effectfulGetter('contexts', 0); -exports.isConnected = effectfulGetter('isConnected', 0); -exports.version = effectfulGetter('version', 0); -exports._newPage = effectfulGetter('newPage', 1); -exports.queryMany_ = effectfulGetter('$$', 1); -exports.query_ = effectfulGetter('$', 1); +exports.unsafeEffectfulGetter = function (prop) { + return function (argsCount) { + return effectfulGetter(prop, argsCount); + }; +}; diff --git a/src/Playwright/Internal.purs b/src/Playwright/Internal.purs new file mode 100644 index 0000000..7e6d76e --- /dev/null +++ b/src/Playwright/Internal.purs @@ -0,0 +1,59 @@ +module Playwright.Internal + ( class NumberOfArgs + , numberOfArgs + , prop + ) + where + +import Type.Proxy +import Prelude + +class NumberOfArgs x where + numberOfArgs :: Proxy x -> Int + +-- More may be added later... +instance numberOfArgsFunction6 :: NumberOfArgs (a -> b -> c -> d -> e -> f -> g) where + numberOfArgs _ = 6 + +else instance numberOfArgsFunction5 :: NumberOfArgs (a -> b -> c -> d -> e -> f) where + numberOfArgs _ = 5 + +else instance numberOfArgsFunction4 :: NumberOfArgs (a -> b -> c -> d -> e) where + numberOfArgs _ = 4 + +else instance numberOfArgsFunction3 :: NumberOfArgs (a -> b -> c -> d) where + numberOfArgs _ = 3 + +else instance numberOfArgsFunction2 :: NumberOfArgs (a -> b -> c) where + numberOfArgs _ = 2 + +else instance numberOfArgsFunction1 :: NumberOfArgs (a -> b) where + numberOfArgs _ = 1 + +else instance numberOfArgsFunction0 :: NumberOfArgs a where + numberOfArgs _ = 0 + +-- | This function is used to prevent "The value of x is undefined here" +-- | errors. +-- | +-- | E.g.: +-- | +-- | ```purescript +-- | url = unsafeEffectfulGetter "url" (countArgs \_ -> url) +-- | ``` +-- | +-- | Thus we can guarantee type safety at least in the number of arguments. +countArgs :: forall a. NumberOfArgs a => (Unit -> a) -> Int +countArgs f = numberOfArgs (proxyOf (f unit)) - 1 + where + proxyOf :: forall a. a -> Proxy a + proxyOf _ = Proxy :: Proxy a + +foreign import unsafeEffectfulGetter + :: forall r + . String + -> Int + -> r + +prop :: forall f r. NumberOfArgs f => String -> (Unit -> f) -> r +prop p f = unsafeEffectfulGetter p (countArgs f) diff --git a/src/Playwright/Logger.purs b/src/Playwright/Logger.purs new file mode 100644 index 0000000..954ef1d --- /dev/null +++ b/src/Playwright/Logger.purs @@ -0,0 +1,43 @@ +module Playwright.Logger where + +import Prelude +import Effect +import Control.Promise +import Data.Options +import Effect.Aff +import Foreign +import Data.Function.Uncurried +import Data.Op +import Data.Functor.Contravariant +import Prim.Row as Row +import Unsafe.Coerce (unsafeCoerce) + +-- foreign import data LoggerOptions :: Type + +data Severity = Verbose | Info | Warning | Error + +showSeverity :: Severity -> String +showSeverity Verbose = "verbose" +showSeverity Info = "info" +showSeverity Warning = "warning" +showSeverity Error = "error" + +-- isEnabled :: Option LoggerOptions (Fn2 String Severity Boolean) +-- isEnabled = opt "isEnabled" + +type LoggerOptions = + ( log :: String -> Severity -> String -> Array String -> Effect Unit + , isEnabled :: String -> Severity -> Boolean + ) + +foreign import data Logger :: Type + +mkLogger + :: forall o t + . Row.Union o t LoggerOptions + => { | o } + -> Logger +mkLogger = unsafeCoerce + +-- log :: Option LoggerOptions (Fn4 String Severity String (Array String) Unit) +-- log = opt "log" diff --git a/src/Playwright/Options.purs b/src/Playwright/Options.purs new file mode 100644 index 0000000..16b0609 --- /dev/null +++ b/src/Playwright/Options.purs @@ -0,0 +1,85 @@ +module Playwright.Options where + +import Data.Options (Option, Options, opt, options) +import Playwright.Data +import Foreign.Object (Object) +import Foreign (Foreign) + +foreign import data Launch :: Type +foreign import data Proxy :: Type + +headless :: Option Launch Boolean +headless = opt "headless" + +executablePath :: Option Launch String +executablePath = opt "executablePath" + +args :: Option Launch String +args = opt "args" + +ignoreDefaultArgs :: Option Launch (Array String) +ignoreDefaultArgs = opt "ignoreDefaultArgs" + +proxy :: Option Launch (Options Proxy) +proxy = opt "proxy" + +downloadsPath :: Option Launch String +downloadsPath = opt "downloadsPath" + +chromiumSandbox :: Option Launch Boolean +chromiumSandbox = opt "chromiumSandbox" + +firefoxUserPrefs :: Option Launch Foreign +firefoxUserPrefs = opt "firefoxUserPrefs" + +handleSIGINT :: Option Launch Boolean +handleSIGINT = opt "handleSIGINT" + +handleSIGTERM :: Option Launch Boolean +handleSIGTERM = opt "handleSIGTERM" + +handleSIGHUP :: Option Launch Boolean +handleSIGHUP = opt "handleSIGHUP" + +timeout :: Option Launch Number +timeout = opt "timeout" + +env :: Option Launch (Object String) +env = opt "env" + +devtools :: Option Launch Boolean +devtools = opt "devtools" + +slowMo :: Option Launch Number +slowMo = opt "slowMo" + +server :: Option Proxy String +server = opt "server" + +bypass :: Option Proxy String +bypass = opt "bypass" + +username :: Option Proxy String +username = opt "username" + +password :: Option Proxy String +password = opt "password" + +foreign import data Newpage :: Type + +foreign import data Screenshot :: Type + +path :: Option Screenshot String +path = opt "path" + +screenshotType :: Option Screenshot ScreenshotType +screenshotType = opt "type" + +quality :: Option Screenshot Number +quality = opt "quality" + +omitBackground :: Option Screenshot Boolean +omitBackground = opt "omitBackground" + +screenshotTimeout :: Option Screenshot Number +screenshotTimeout = opt "timeout" diff --git a/test/Main.purs b/test/Main.purs index 86f9091..f0267de 100644 --- a/test/Main.purs +++ b/test/Main.purs @@ -1,13 +1,12 @@ module Test.Main where import Prelude +import Playwright (close, firefox, headless, launch, slowMo) import Effect (Effect) -import Effect.Console (log) -import Test.Unit.Assert as Assert -import Playwright -import Data.Options -import Test.Unit -import Test.Unit.Main +import Data.Options ((:=)) +import Test.Unit (suite, test) +import Test.Unit.Main (runTest) +import Untagged.Union (asOneOf) main :: Effect Unit main = runTest do @@ -16,6 +15,4 @@ main = runTest do browser <- launch firefox $ headless := false <> slowMo := 100.0 - - close browser - pure unit + close $ asOneOf browser diff --git a/test/static/index.html b/test/static/index.html new file mode 100644 index 0000000..c8a4f96 --- /dev/null +++ b/test/static/index.html @@ -0,0 +1,8 @@ + + + + + +
+ +