diff --git a/ffi/shared.js b/ffi/shared.js deleted file mode 100644 index 4734af5..0000000 --- a/ffi/shared.js +++ /dev/null @@ -1,4 +0,0 @@ -/** @template T */ -export class Maybe {} - -export class Milliseconds {} diff --git a/spago.dhall b/spago.dhall index 31e6b02..260fe2e 100644 --- a/spago.dhall +++ b/spago.dhall @@ -41,7 +41,9 @@ to generate this file without the comments in this block. , "prelude" , "simple-json" , "spec" + , "st" , "strings" + , "tailrec" , "transformers" , "tuples" , "unsafe-coerce" diff --git a/src/Puppeteer.Base.purs b/src/Puppeteer.Base.purs index 11a5949..5a4ffed 100644 --- a/src/Puppeteer.Base.purs +++ b/src/Puppeteer.Base.purs @@ -51,14 +51,14 @@ type Viewport = prepareViewport :: Viewport -> Foreign prepareViewport { deviceScaleFactor, hasTouch, height, width, isLandscape, isMobile } = - writeImpl - { deviceScaleFactor: FFI.maybeToUndefined deviceScaleFactor - , hasTouch: FFI.maybeToUndefined hasTouch - , isLandscape: FFI.maybeToUndefined isLandscape - , isMobile: FFI.maybeToUndefined isMobile - , height - , width - } + writeImpl + { deviceScaleFactor: FFI.maybeToUndefined deviceScaleFactor + , hasTouch: FFI.maybeToUndefined hasTouch + , isLandscape: FFI.maybeToUndefined isLandscape + , isMobile: FFI.maybeToUndefined isMobile + , height + , width + } --| [`PuppeteerNode`](https://pptr.dev/api/puppeteer.puppeteernode) foreign import data Puppeteer :: Row Type -> Type diff --git a/src/Puppeteer.Eval.js b/src/Puppeteer.Eval.js index 8908e34..072790f 100644 --- a/src/Puppeteer.Eval.js +++ b/src/Puppeteer.Eval.js @@ -1,8 +1,8 @@ -import {Page, JSHandle} from 'puppeteer' +import { Page, JSHandle } from 'puppeteer' -/** -* @type {(_: string) => (_: Page | JSHandle) => (_: Array) => Promise} -*/ +/** + * @type {(_: string) => (_: Page | JSHandle) => (_: Array) => Promise} + */ export const _run = s => h => a => { /** @type {any} */ const f = new Function(`return (${s})(...arguments)`) @@ -11,9 +11,9 @@ export const _run = s => h => a => { return ev(f, ...a) } -/** -* @type {(_: string) => (_: Page | JSHandle) => (_: Array) => Promise>} -*/ +/** + * @type {(_: string) => (_: Page | JSHandle) => (_: Array) => Promise>} + */ export const _runh = s => h => a => { /** @type {any} */ const f = new Function(`return (${s})(...arguments)`) diff --git a/src/Puppeteer.Eval.purs b/src/Puppeteer.Eval.purs index 7f4ea31..908a692 100644 --- a/src/Puppeteer.Eval.purs +++ b/src/Puppeteer.Eval.purs @@ -27,19 +27,19 @@ unsafeRunJsHandle0 :: forall e @r. EvalTarget e => String -> e -> Aff (Handle r) unsafeRunJsHandle0 js h = Promise.toAff $ _runh js (unsafeToForeign h) [] unsafeRunJs1 :: forall a e @r. EvalTarget e => String -> a -> e -> Aff r -unsafeRunJs1 js a h = Promise.toAff $ _run js (unsafeToForeign h) [unsafeToForeign a] +unsafeRunJs1 js a h = Promise.toAff $ _run js (unsafeToForeign h) [ unsafeToForeign a ] unsafeRunJsHandle1 :: forall a e @r. EvalTarget e => String -> a -> e -> Aff (Handle r) -unsafeRunJsHandle1 js a h = Promise.toAff $ _runh js (unsafeToForeign h) [unsafeToForeign a] +unsafeRunJsHandle1 js a h = Promise.toAff $ _runh js (unsafeToForeign h) [ unsafeToForeign a ] -unsafeRunJs2 :: forall a b e @r. EvalTarget e => String -> a -> b-> e -> Aff r -unsafeRunJs2 js a b h = Promise.toAff $ _run js (unsafeToForeign h) [unsafeToForeign a, unsafeToForeign b] +unsafeRunJs2 :: forall a b e @r. EvalTarget e => String -> a -> b -> e -> Aff r +unsafeRunJs2 js a b h = Promise.toAff $ _run js (unsafeToForeign h) [ unsafeToForeign a, unsafeToForeign b ] unsafeRunJsHandle2 :: forall a b e @r. EvalTarget e => String -> a -> b -> e -> Aff (Handle r) -unsafeRunJsHandle2 js a b h = Promise.toAff $ _runh js (unsafeToForeign h) [unsafeToForeign a, unsafeToForeign b] +unsafeRunJsHandle2 js a b h = Promise.toAff $ _runh js (unsafeToForeign h) [ unsafeToForeign a, unsafeToForeign b ] unsafeRunJs3 :: forall a b c e @r. EvalTarget e => String -> a -> b -> c -> e -> Aff r -unsafeRunJs3 js a b c h = Promise.toAff $ _run js (unsafeToForeign h) [unsafeToForeign a, unsafeToForeign b, unsafeToForeign c] +unsafeRunJs3 js a b c h = Promise.toAff $ _run js (unsafeToForeign h) [ unsafeToForeign a, unsafeToForeign b, unsafeToForeign c ] unsafeRunJsHandle3 :: forall a b c e @r. EvalTarget e => String -> a -> b -> c -> e -> Aff (Handle r) -unsafeRunJsHandle3 js a b c h = Promise.toAff $ _runh js (unsafeToForeign h) [unsafeToForeign a, unsafeToForeign b, unsafeToForeign c] +unsafeRunJsHandle3 js a b c h = Promise.toAff $ _runh js (unsafeToForeign h) [ unsafeToForeign a, unsafeToForeign b, unsafeToForeign c ] diff --git a/src/Puppeteer.FFI.js b/src/Puppeteer.FFI.js index f8f389c..c3409c2 100644 --- a/src/Puppeteer.FFI.js +++ b/src/Puppeteer.FFI.js @@ -10,7 +10,7 @@ export const mergeRecords = rs => /** @type {(m: Array<{k: string, v: any}>) => Record} */ export const _mapToRecord = map => - map.reduce((r, {k, v}) => { + map.reduce((r, { k, v }) => { r[k] = v return r }, emptyRecord()) diff --git a/src/Puppeteer.FFI.purs b/src/Puppeteer.FFI.purs index cc86638..9675bcc 100644 --- a/src/Puppeteer.FFI.purs +++ b/src/Puppeteer.FFI.purs @@ -15,14 +15,14 @@ import Foreign (Foreign) import Simple.JSON (class WriteForeign) foreign import mergeRecords :: Array Foreign -> Foreign -foreign import _mapToRecord :: forall a. Array {k :: String, v :: a} -> Foreign +foreign import _mapToRecord :: forall a. Array { k :: String, v :: a } -> Foreign foreign import _maybeToUndefined :: forall a. (Maybe a -> Nullable a) -> Maybe a -> Foreign makeMap :: forall k v. Ord k => Array { k :: k, v :: v } -> Map k v makeMap = Map.fromFoldable <<< map (\{ k, v } -> Tuple k v) mapToRecord :: forall a. WriteForeign a => Map String a -> Foreign -mapToRecord = _mapToRecord <<< foldlWithIndex (\k a v -> Array.cons {k, v} a) [] +mapToRecord = _mapToRecord <<< foldlWithIndex (\k a v -> Array.cons { k, v } a) [] maybeToUndefined :: forall a. WriteForeign a => Maybe a -> Foreign maybeToUndefined = _maybeToUndefined Nullable.toNullable diff --git a/src/Puppeteer.Handle.HTML.purs b/src/Puppeteer.Handle.HTML.purs index 0b45c3a..ce5ecdd 100644 --- a/src/Puppeteer.Handle.HTML.purs +++ b/src/Puppeteer.Handle.HTML.purs @@ -81,35 +81,37 @@ offsetHeight :: forall a. IsElement a => Handle a -> Aff (Array Number) offsetHeight = Eval.unsafeRunJs0 "e => e.offsetHeight" attrs :: forall a. IsElement a => Handle a -> Aff (Map String String) -attrs = let - js = String.joinWith "\n" - [ "e => Array.from(e.attributes)" - , " .reduce(" - , " (m, a) => [...m, {k: a.name, v: a.value}]," - , " []," - , " )" - , " .filter(({k}) => k)" - ] +attrs = + let + js = String.joinWith "\n" + [ "e => Array.from(e.attributes)" + , " .reduce(" + , " (m, a) => [...m, {k: a.name, v: a.value}]," + , " []," + , " )" + , " .filter(({k}) => k)" + ] in - map FFI.makeMap <<< Eval.unsafeRunJs0 @(Array {k :: String, v :: String}) js + map FFI.makeMap <<< Eval.unsafeRunJs0 @(Array { k :: String, v :: String }) js computedStyle :: forall a. IsElement a => Handle a -> Aff (Map String String) -computedStyle = let +computedStyle = + let js = String.joinWith "\n" - [ "e => {" - , " const s = window.getComputedStyle(e)" - , " const a = []" - , " for (let i = 0; i < s.length; i++) {" - , " const k = s.item(i)" - , " const v = s.getPropertyValue(k)" - , " a.push({k, v})" - , " }" - , "" - , " return a" - , "}" - ] + [ "e => {" + , " const s = window.getComputedStyle(e)" + , " const a = []" + , " for (let i = 0; i < s.length; i++) {" + , " const k = s.item(i)" + , " const v = s.getPropertyValue(k)" + , " a.push({k, v})" + , " }" + , "" + , " return a" + , "}" + ] in - map FFI.makeMap <<< Eval.unsafeRunJs0 @(Array {k :: String, v :: String}) js + map FFI.makeMap <<< Eval.unsafeRunJs0 @(Array { k :: String, v :: String }) js value :: Handle HTML.HTMLInputElement -> Aff String value = Eval.unsafeRunJs0 "e => e.value" diff --git a/src/Puppeteer.Handle.js b/src/Puppeteer.Handle.js index 364c95c..4366a2e 100644 --- a/src/Puppeteer.Handle.js +++ b/src/Puppeteer.Handle.js @@ -4,14 +4,17 @@ import { ElementHandle } from 'puppeteer' import { JSHandle } from 'puppeteer' /** @type {(_: { remoteObject: (_0: string) => T, primitive: (_0: unknown) => T }) => (_: JSHandle) => () => T} */ -export const _id = ({remoteObject, primitive}) => h => () => { - const oid = h.remoteObject().objectId - if (oid) { - return remoteObject(oid) - } else { - return primitive(h.remoteObject().value) +export const _id = + ({ remoteObject, primitive }) => + h => + () => { + const oid = h.remoteObject().objectId + if (oid) { + return remoteObject(oid) + } else { + return primitive(h.remoteObject().value) + } } -} /** @type {(_: string) => (_: ElementHandle) => Promise>>} */ export const _find = s => h => h.$$(s) diff --git a/src/Puppeteer.Handle.purs b/src/Puppeteer.Handle.purs index 097dbeb..75ba53d 100644 --- a/src/Puppeteer.Handle.purs +++ b/src/Puppeteer.Handle.purs @@ -60,7 +60,7 @@ import Web.HTML (HTMLElement) import Web.HTML as HTML id :: forall a. Handle a -> Effect HandleId -id = _id {remoteObject: HandleObject, primitive: HandlePrimitive} +id = _id { remoteObject: HandleObject, primitive: HandlePrimitive } data HandleId = HandleObject String diff --git a/src/Puppeteer.Page.Event.ConsoleMessage.purs b/src/Puppeteer.Page.Event.ConsoleMessage.purs index ef7f8f2..707dd90 100644 --- a/src/Puppeteer.Page.Event.ConsoleMessage.purs +++ b/src/Puppeteer.Page.Event.ConsoleMessage.purs @@ -14,7 +14,9 @@ import Prelude import Control.Monad.Except (runExcept) import Data.Array as Array import Data.Either (hush) +import Data.Generic.Rep (class Generic) import Data.Maybe (Maybe) +import Data.Show.Generic (genericShow) import Foreign (Foreign, unsafeFromForeign) import Puppeteer.Base (Handle) import Puppeteer.FFI as FFI @@ -54,6 +56,10 @@ data MessageType | TimeEnd | Verbose +derive instance eqMessageType :: Eq MessageType +derive instance genericMessageType :: Generic MessageType _ +instance showMessageType :: Show MessageType where show = genericShow + messageTypeOfString :: String -> MessageType messageTypeOfString "debug" = Debug messageTypeOfString "info" = Info diff --git a/src/Puppeteer.Page.Event.purs b/src/Puppeteer.Page.Event.purs index 3e0c21e..abd7ea1 100644 --- a/src/Puppeteer.Page.Event.purs +++ b/src/Puppeteer.Page.Event.purs @@ -41,7 +41,8 @@ import Puppeteer.Page.Event.Dialog (Dialog) import Simple.JSON (class ReadForeign, readImpl) connectPageConsole :: Page -> Aff Unit -connectPageConsole p = let +connectPageConsole p = + let onmsg m = do title <- Page.title p let t = ConsoleMessage.messageType m diff --git a/src/Puppeteer.Page.purs b/src/Puppeteer.Page.purs index a9b8c0e..447f8b6 100644 --- a/src/Puppeteer.Page.purs +++ b/src/Puppeteer.Page.purs @@ -138,7 +138,7 @@ close = Promise.toAff <<< _close content :: Page -> Aff String content = Promise.toAff <<< _content -setContent :: String -> LifecycleEvent -> Page -> Aff Unit +setContent :: String -> LifecycleEvent -> Page -> Aff Unit setContent s ev = Promise.toAff <<< _setContent s (prepareLifecycleEvent ev) setViewport :: Viewport -> Page -> Aff Unit diff --git a/test/Puppeteer.Page.Event.Spec.purs b/test/Puppeteer.Page.Event.Spec.purs new file mode 100644 index 0000000..9423c89 --- /dev/null +++ b/test/Puppeteer.Page.Event.Spec.purs @@ -0,0 +1,78 @@ +module Puppeteer.Page.Event.Spec where + +import Prelude + +import Control.Monad.Error.Class (liftMaybe) +import Control.Monad.Rec.Class (untilJust) +import Control.Monad.ST.Class (liftST) +import Control.Monad.ST.Global as ST +import Control.Monad.ST.Ref as ST.Ref +import Data.Array as Array +import Data.Array.NonEmpty as NonEmptyArray +import Data.Map as Map +import Data.Maybe (Maybe(..)) +import Data.Newtype (wrap) +import Data.Time.Duration (Milliseconds(..)) +import Effect (Effect) +import Effect.Aff (Aff, forkAff, joinFiber, makeAff) +import Effect.Class (liftEffect) +import Effect.Exception (error) +import Effect.Exception as Error +import Puppeteer as Pup +import Puppeteer.Base (timeoutThrow) +import Puppeteer.Handle as Pup.Handle +import Puppeteer.Handle.HTML as Pup.Handle.HTML +import Puppeteer.Keyboard as Pup.Keyboard +import Puppeteer.Page as Pup.Page +import Puppeteer.Page.Event (connectPageConsole) +import Puppeteer.Page.Event as Pup.Page.Event +import Puppeteer.Page.Event.ConsoleMessage as ConsoleMessage +import Puppeteer.Page.WaitFor as Pup.Page.WaitFor +import Test.Spec (SpecT, afterAll, beforeAll, describe) +import Test.Spec.Assertions (shouldEqual) +import Test.Util (failOnPageError, test) + +scriptError :: String +scriptError = "throw new Error('eek!')" + +scriptLog :: String +scriptLog = "console.log('beak')" + +listenIntoSTArray :: forall e ed. Pup.Page.Event.Event e ed => e -> Pup.Page -> Aff ({ st :: ST.Ref.STRef ST.Global (Array ed), cleanup :: Aff Unit }) +listenIntoSTArray e p = do + st <- liftST $ ST.Ref.new [] + let + handle ed = do + eds <- liftST $ ST.Ref.read st + _ <- liftST $ ST.Ref.write (eds <> [ ed ]) st + pure unit + t <- Pup.Page.Event.listen e handle p + pure { st, cleanup: Pup.closeContext t } + +spec :: SpecT Aff Unit Effect Unit +spec = + beforeAll (Pup.Page.new =<< Pup.launch_ =<< Pup.puppeteer unit) + $ afterAll Pup.Page.close + $ describe "Page" do + test "listen, PageError" \p -> do + { st: errsST, cleanup } <- listenIntoSTArray Pup.Page.Event.PageError p + _ <- Pup.Page.addScriptTag (Pup.Page.AddScriptInline Pup.Page.Script scriptError) p + err <- timeoutThrow (wrap 1000.0) + $ untilJust do + errs <- liftST $ ST.Ref.read errsST + pure $ Array.head errs + Error.message err `shouldEqual` "eek!" + cleanup + + test "once" \p -> do + errF <- forkAff $ Pup.Page.Event.once Pup.Page.Event.PageError p + _ <- Pup.Page.addScriptTag (Pup.Page.AddScriptInline Pup.Page.Script scriptError) p + err <- joinFiber errF + Error.message err `shouldEqual` "eek!" + + test "Console" \p -> do + logF <- forkAff $ Pup.Page.Event.once Pup.Page.Event.Console p + _ <- Pup.Page.addScriptTag (Pup.Page.AddScriptInline Pup.Page.Script scriptLog) p + log <- joinFiber logF + ConsoleMessage.text log `shouldEqual` "beak" + ConsoleMessage.messageType log `shouldEqual` ConsoleMessage.Log diff --git a/test/Puppeteer.Page.Spec.purs b/test/Puppeteer.Page.Spec.purs index 39e5b38..9915f22 100644 --- a/test/Puppeteer.Page.Spec.purs +++ b/test/Puppeteer.Page.Spec.purs @@ -105,13 +105,15 @@ spec = beforeAll (Pup.launch_ =<< Pup.puppeteer unit) test "setViewport, viewport" \b -> do p <- Pup.Page.new b - let vp = { deviceScaleFactor: Nothing - , hasTouch: Nothing - , height: 1200 - , width: 800 - , isLandscape: Nothing - , isMobile: Nothing - } + let + vp = + { deviceScaleFactor: Nothing + , hasTouch: Nothing + , height: 1200 + , width: 800 + , isLandscape: Nothing + , isMobile: Nothing + } Pup.Page.setViewport vp p vp' <- liftMaybe (error "no viewport!") $ Pup.Page.viewport p diff --git a/test/Test.Main.purs b/test/Test.Main.purs index c9e01fb..e88408b 100644 --- a/test/Test.Main.purs +++ b/test/Test.Main.purs @@ -18,6 +18,7 @@ import Node.Process as Process import Node.Stream as Writable import Puppeteer.Browser.Spec as Spec.Browser import Puppeteer.Page.Spec as Spec.Page +import Puppeteer.Page.Event.Spec as Spec.Page.Event import Puppeteer.Spec as Spec import Test.Spec (SpecT) import Test.Spec.Config (defaultConfig) @@ -32,6 +33,7 @@ specs = do Spec.spec Spec.Browser.spec Spec.Page.spec + Spec.Page.Event.spec main :: Effect Unit main = launchAff_ do diff --git a/test/Test.Util.purs b/test/Test.Util.purs index 6f2a8ba..ef8de29 100644 --- a/test/Test.Util.purs +++ b/test/Test.Util.purs @@ -27,7 +27,8 @@ testA :: forall m t arg g. MonadAff g => Monad m => Example t arg Aff => String testA = test_ liftAff failOnPageError :: forall a. Pup.Page -> Aff a -> Aff a -failOnPageError p a = let +failOnPageError p a = + let ok = parallel $ try a err = parallel $ Left <$> Pup.Page.Event.once Pup.Page.Event.PageError p in