feat: add plugins
This commit is contained in:
parent
88f28d3424
commit
70ec5f5273
1
.gitignore
vendored
1
.gitignore
vendored
@ -10,3 +10,4 @@
|
|||||||
/.spago
|
/.spago
|
||||||
.log
|
.log
|
||||||
.purs-repl
|
.purs-repl
|
||||||
|
.env
|
||||||
|
@ -15,9 +15,14 @@
|
|||||||
"typescript": "^5.0.0"
|
"typescript": "^5.0.0"
|
||||||
},
|
},
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
|
"@cliqz/adblocker-puppeteer": "1.23.8",
|
||||||
"callsites": "^4.1.0",
|
"callsites": "^4.1.0",
|
||||||
"puppeteer": "^21.3.5",
|
"puppeteer": "^21.3.5",
|
||||||
"puppeteer-core": "^21.3.5",
|
"puppeteer-core": "^21.3.5",
|
||||||
"puppeteer-extra": "^3.3.6"
|
"puppeteer-extra": "^3.3.6",
|
||||||
|
"puppeteer-extra-plugin-adblocker": "^2.13.6",
|
||||||
|
"puppeteer-extra-plugin-anonymize-ua": "^2.4.6",
|
||||||
|
"puppeteer-extra-plugin-recaptcha": "^3.6.8",
|
||||||
|
"puppeteer-extra-plugin-stealth": "^2.11.2"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
105
packages.dhall
105
packages.dhall
@ -1,105 +0,0 @@
|
|||||||
{-
|
|
||||||
Welcome to your new Dhall package-set!
|
|
||||||
|
|
||||||
Below are instructions for how to edit this file for most use
|
|
||||||
cases, so that you don't need to know Dhall to use it.
|
|
||||||
|
|
||||||
## Use Cases
|
|
||||||
|
|
||||||
Most will want to do one or both of these options:
|
|
||||||
1. Override/Patch a package's dependency
|
|
||||||
2. Add a package not already in the default package set
|
|
||||||
|
|
||||||
This file will continue to work whether you use one or both options.
|
|
||||||
Instructions for each option are explained below.
|
|
||||||
|
|
||||||
### Overriding/Patching a package
|
|
||||||
|
|
||||||
Purpose:
|
|
||||||
- Change a package's dependency to a newer/older release than the
|
|
||||||
default package set's release
|
|
||||||
- Use your own modified version of some dependency that may
|
|
||||||
include new API, changed API, removed API by
|
|
||||||
using your custom git repo of the library rather than
|
|
||||||
the package set's repo
|
|
||||||
|
|
||||||
Syntax:
|
|
||||||
where `entityName` is one of the following:
|
|
||||||
- dependencies
|
|
||||||
- repo
|
|
||||||
- version
|
|
||||||
-------------------------------
|
|
||||||
let upstream = --
|
|
||||||
in upstream
|
|
||||||
with packageName.entityName = "new value"
|
|
||||||
-------------------------------
|
|
||||||
|
|
||||||
Example:
|
|
||||||
-------------------------------
|
|
||||||
let upstream = --
|
|
||||||
in upstream
|
|
||||||
with halogen.version = "master"
|
|
||||||
with halogen.repo = "https://example.com/path/to/git/repo.git"
|
|
||||||
|
|
||||||
with halogen-vdom.version = "v4.0.0"
|
|
||||||
with halogen-vdom.dependencies = [ "extra-dependency" ] # halogen-vdom.dependencies
|
|
||||||
-------------------------------
|
|
||||||
|
|
||||||
### Additions
|
|
||||||
|
|
||||||
Purpose:
|
|
||||||
- Add packages that aren't already included in the default package set
|
|
||||||
|
|
||||||
Syntax:
|
|
||||||
where `<version>` is:
|
|
||||||
- a tag (i.e. "v4.0.0")
|
|
||||||
- a branch (i.e. "master")
|
|
||||||
- commit hash (i.e. "701f3e44aafb1a6459281714858fadf2c4c2a977")
|
|
||||||
-------------------------------
|
|
||||||
let upstream = --
|
|
||||||
in upstream
|
|
||||||
with new-package-name =
|
|
||||||
{ dependencies =
|
|
||||||
[ "dependency1"
|
|
||||||
, "dependency2"
|
|
||||||
]
|
|
||||||
, repo =
|
|
||||||
"https://example.com/path/to/git/repo.git"
|
|
||||||
, version =
|
|
||||||
"<version>"
|
|
||||||
}
|
|
||||||
-------------------------------
|
|
||||||
|
|
||||||
Example:
|
|
||||||
-------------------------------
|
|
||||||
let upstream = --
|
|
||||||
in upstream
|
|
||||||
with benchotron =
|
|
||||||
{ dependencies =
|
|
||||||
[ "arrays"
|
|
||||||
, "exists"
|
|
||||||
, "profunctor"
|
|
||||||
, "strings"
|
|
||||||
, "quickcheck"
|
|
||||||
, "lcg"
|
|
||||||
, "transformers"
|
|
||||||
, "foldable-traversable"
|
|
||||||
, "exceptions"
|
|
||||||
, "node-fs"
|
|
||||||
, "node-buffer"
|
|
||||||
, "node-readline"
|
|
||||||
, "datetime"
|
|
||||||
, "now"
|
|
||||||
]
|
|
||||||
, repo =
|
|
||||||
"https://github.com/hdgarrood/purescript-benchotron.git"
|
|
||||||
, version =
|
|
||||||
"v7.0.0"
|
|
||||||
}
|
|
||||||
-------------------------------
|
|
||||||
-}
|
|
||||||
let upstream =
|
|
||||||
https://github.com/purescript/package-sets/releases/download/psc-0.15.10-20230921/packages.dhall
|
|
||||||
sha256:8c2123d78b41b74a5599f220cf526b48003804a490a85c324fd6a25215a94084
|
|
||||||
|
|
||||||
in upstream
|
|
55
spago.dhall
55
spago.dhall
@ -1,55 +0,0 @@
|
|||||||
{-
|
|
||||||
Welcome to a Spago project!
|
|
||||||
You can edit this file as you like.
|
|
||||||
|
|
||||||
Need help? See the following resources:
|
|
||||||
- Spago documentation: https://github.com/purescript/spago
|
|
||||||
- Dhall language tour: https://docs.dhall-lang.org/tutorials/Language-Tour.html
|
|
||||||
|
|
||||||
When creating a new Spago project, you can use
|
|
||||||
`spago init --no-comments` or `spago init -C`
|
|
||||||
to generate this file without the comments in this block.
|
|
||||||
-}
|
|
||||||
{ name = "my-project"
|
|
||||||
, dependencies =
|
|
||||||
[ "aff"
|
|
||||||
, "aff-promise"
|
|
||||||
, "arrays"
|
|
||||||
, "bifunctors"
|
|
||||||
, "console"
|
|
||||||
, "control"
|
|
||||||
, "datetime"
|
|
||||||
, "effect"
|
|
||||||
, "either"
|
|
||||||
, "enums"
|
|
||||||
, "exceptions"
|
|
||||||
, "filterable"
|
|
||||||
, "foldable-traversable"
|
|
||||||
, "foreign"
|
|
||||||
, "identity"
|
|
||||||
, "integers"
|
|
||||||
, "maybe"
|
|
||||||
, "newtype"
|
|
||||||
, "node-buffer"
|
|
||||||
, "node-path"
|
|
||||||
, "node-process"
|
|
||||||
, "node-streams"
|
|
||||||
, "nullable"
|
|
||||||
, "ordered-collections"
|
|
||||||
, "parallel"
|
|
||||||
, "prelude"
|
|
||||||
, "simple-json"
|
|
||||||
, "spec"
|
|
||||||
, "st"
|
|
||||||
, "strings"
|
|
||||||
, "tailrec"
|
|
||||||
, "transformers"
|
|
||||||
, "tuples"
|
|
||||||
, "unsafe-coerce"
|
|
||||||
, "web-cssom"
|
|
||||||
, "web-dom"
|
|
||||||
, "web-html"
|
|
||||||
]
|
|
||||||
, packages = ./packages.dhall
|
|
||||||
, sources = [ "src/**/*.purs", "test/**/*.purs" ]
|
|
||||||
}
|
|
46
spago.yaml
Normal file
46
spago.yaml
Normal file
@ -0,0 +1,46 @@
|
|||||||
|
package:
|
||||||
|
dependencies:
|
||||||
|
- aff
|
||||||
|
- aff-promise
|
||||||
|
- arrays
|
||||||
|
- bifunctors
|
||||||
|
- console
|
||||||
|
- control
|
||||||
|
- datetime
|
||||||
|
- dotenv
|
||||||
|
- effect
|
||||||
|
- either
|
||||||
|
- enums
|
||||||
|
- exceptions
|
||||||
|
- filterable
|
||||||
|
- foldable-traversable
|
||||||
|
- foreign
|
||||||
|
- identity
|
||||||
|
- integers
|
||||||
|
- maybe
|
||||||
|
- newtype
|
||||||
|
- node-buffer
|
||||||
|
- node-path
|
||||||
|
- node-process
|
||||||
|
- node-streams
|
||||||
|
- nullable
|
||||||
|
- ordered-collections
|
||||||
|
- parallel
|
||||||
|
- prelude
|
||||||
|
- simple-json
|
||||||
|
- spec
|
||||||
|
- st
|
||||||
|
- strings
|
||||||
|
- tailrec
|
||||||
|
- transformers
|
||||||
|
- tuples
|
||||||
|
- unsafe-coerce
|
||||||
|
- web-cssom
|
||||||
|
- web-dom
|
||||||
|
- web-html
|
||||||
|
name: puppeteer
|
||||||
|
workspace:
|
||||||
|
extra_packages: {}
|
||||||
|
package_set:
|
||||||
|
url: https://raw.githubusercontent.com/purescript/package-sets/psc-0.15.10-20230921/packages.json
|
||||||
|
hash: sha256-pb4kxdVVOLZIDNaKgTY0oEdPEZlHuEvNj2xOz2nwMnM=
|
@ -3,3 +3,6 @@ export const unsafeLog = a => {
|
|||||||
console.log(a)
|
console.log(a)
|
||||||
return a
|
return a
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/** @type {<A extends object, B extends object>(a: A) => (b: B) => A & B} */
|
||||||
|
export const unsafeUnion = a => b => ({ ...a, ...b })
|
||||||
|
@ -4,19 +4,45 @@ import Prelude
|
|||||||
|
|
||||||
import Control.Alt ((<|>))
|
import Control.Alt ((<|>))
|
||||||
import Control.Monad.Error.Class (liftMaybe, try)
|
import Control.Monad.Error.Class (liftMaybe, try)
|
||||||
|
import Control.Monad.Except (runExcept)
|
||||||
import Control.Parallel (parallel, sequential)
|
import Control.Parallel (parallel, sequential)
|
||||||
import Data.Either (hush)
|
import Data.Bifunctor (lmap)
|
||||||
|
import Data.Either (Either(..), hush)
|
||||||
|
import Data.Map (Map)
|
||||||
import Data.Maybe (Maybe(..))
|
import Data.Maybe (Maybe(..))
|
||||||
import Data.Time.Duration (Milliseconds)
|
import Data.Time.Duration (Milliseconds)
|
||||||
import Effect.Aff (Aff, delay)
|
import Effect.Aff (Aff, delay)
|
||||||
import Effect.Exception (error)
|
import Effect.Exception (Error, error)
|
||||||
import Foreign (Foreign, unsafeFromForeign)
|
import Foreign (Foreign, unsafeFromForeign)
|
||||||
|
import Foreign.Object (Object)
|
||||||
|
import Foreign.Object as Object
|
||||||
import Prim.Row (class Union)
|
import Prim.Row (class Union)
|
||||||
import Puppeteer.FFI as FFI
|
import Puppeteer.FFI as FFI
|
||||||
import Simple.JSON (class ReadForeign, writeImpl)
|
import Simple.JSON (class ReadForeign, class WriteForeign, readImpl, writeImpl)
|
||||||
import Web.HTML as HTML
|
import Web.HTML as HTML
|
||||||
|
|
||||||
foreign import unsafeLog :: forall a. a -> a
|
foreign import unsafeLog :: forall a. a -> a
|
||||||
|
foreign import unsafeUnion :: forall a b c. a -> b -> c
|
||||||
|
|
||||||
|
data JsDuplex a ir = JsDuplex
|
||||||
|
{ from :: ir -> Either String a
|
||||||
|
, into :: a -> ir
|
||||||
|
}
|
||||||
|
|
||||||
|
duplex :: forall a ir. (a -> ir) -> (ir -> Either String a) -> JsDuplex a ir
|
||||||
|
duplex into from = JsDuplex { from, into }
|
||||||
|
|
||||||
|
duplexRead :: forall a ir. ReadForeign ir => JsDuplex a ir -> Foreign -> Either Error a
|
||||||
|
duplexRead (JsDuplex { from }) = lmap error <<< flip bind from <<< lmap show <<< runExcept <<< readImpl
|
||||||
|
|
||||||
|
duplexWrite :: forall a ir. WriteForeign ir => JsDuplex a ir -> a -> Foreign
|
||||||
|
duplexWrite (JsDuplex { into }) = writeImpl <<< into
|
||||||
|
|
||||||
|
mapToObject :: forall v. WriteForeign v => Map String v -> Object Foreign
|
||||||
|
mapToObject = Object.fromFoldableWithIndex <<< map writeImpl
|
||||||
|
|
||||||
|
merge :: forall a b c. Union a b c => Record a -> Record b -> Record c
|
||||||
|
merge a b = unsafeUnion a b
|
||||||
|
|
||||||
timeout :: forall a. Milliseconds -> Aff a -> Aff (Maybe a)
|
timeout :: forall a. Milliseconds -> Aff a -> Aff (Maybe a)
|
||||||
timeout t a =
|
timeout t a =
|
||||||
@ -30,10 +56,10 @@ timeoutThrow t a = liftMaybe (error "timeout") =<< timeout t a
|
|||||||
|
|
||||||
newtype Context (a :: Symbol) = Context (Unit -> Aff Unit)
|
newtype Context (a :: Symbol) = Context (Unit -> Aff Unit)
|
||||||
|
|
||||||
instance semicontext :: Semigroup (Context a) where
|
instance Semigroup (Context a) where
|
||||||
append _ a = a
|
append _ a = a
|
||||||
|
|
||||||
instance monoidcontext :: Monoid (Context a) where
|
instance Monoid (Context a) where
|
||||||
mempty = Context $ const $ pure unit
|
mempty = Context $ const $ pure unit
|
||||||
|
|
||||||
closeContext :: forall (a :: Symbol). Context a -> Aff Unit
|
closeContext :: forall (a :: Symbol). Context a -> Aff Unit
|
||||||
@ -50,122 +76,51 @@ type Viewport =
|
|||||||
, isMobile :: Maybe Boolean
|
, isMobile :: Maybe Boolean
|
||||||
}
|
}
|
||||||
|
|
||||||
prepareViewport :: Viewport -> Foreign
|
duplexViewport :: JsDuplex Viewport Viewport
|
||||||
prepareViewport { deviceScaleFactor, hasTouch, height, width, isLandscape, isMobile } =
|
duplexViewport = duplex identity pure
|
||||||
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)
|
--| [`PuppeteerNode`](https://pptr.dev/api/puppeteer.puppeteernode)
|
||||||
foreign import data Puppeteer :: Row Type -> Type
|
foreign import data Puppeteer :: Row Type -> Type
|
||||||
|
|
||||||
data LifecycleEvent = Load | DomContentLoaded | NetworkIdleZeroConnections | NetworkIdleAtMostTwoConnections
|
data LifecycleEvent = Load | DomContentLoaded | NetworkIdleZeroConnections | NetworkIdleAtMostTwoConnections
|
||||||
|
|
||||||
prepareLifecycleEvent :: LifecycleEvent -> Foreign
|
duplexLifecycleEvent :: JsDuplex LifecycleEvent String
|
||||||
prepareLifecycleEvent Load = writeImpl "load"
|
duplexLifecycleEvent =
|
||||||
prepareLifecycleEvent DomContentLoaded = writeImpl "domcontentloaded"
|
let
|
||||||
prepareLifecycleEvent NetworkIdleZeroConnections = writeImpl "networkidle0"
|
toString Load = "load"
|
||||||
prepareLifecycleEvent NetworkIdleAtMostTwoConnections = writeImpl "networkidle2"
|
toString DomContentLoaded = "domcontentloaded"
|
||||||
|
toString NetworkIdleZeroConnections = "networkidle0"
|
||||||
--| A puppeteer plugin
|
toString NetworkIdleAtMostTwoConnections = "networkidle2"
|
||||||
--|
|
fromString "load" = Right Load
|
||||||
--| [`puppeteer-extra`](https://github.com/berstend/puppeteer-extra/tree/master/packages/puppeteer-extra-plugin)
|
fromString "domcontentloaded" = Right DomContentLoaded
|
||||||
--|
|
fromString "networkidle0" = Right NetworkIdleZeroConnections
|
||||||
--| `src/DebugPlugin.js`
|
fromString "networkidle2" = Right NetworkIdleAtMostTwoConnections
|
||||||
--| ```javascript
|
fromString o = Left $ "unknown lifecycle event " <> o
|
||||||
--| import { PuppeteerExtraPlugin } from 'puppeteer-extra-plugin'
|
in
|
||||||
--| import { PuppeteerExtra } from 'puppeteer-extra'
|
duplex toString fromString
|
||||||
--| import { Page } from 'puppeteer'
|
|
||||||
--|
|
|
||||||
--| /** @typedef {Page & {sayHello: () => void}} DebugPluginPage */
|
|
||||||
--|
|
|
||||||
--| class DebugPlugin extends PuppeteerExtraPlugin {
|
|
||||||
--| name = 'hello-world'
|
|
||||||
--|
|
|
||||||
--| constructor(opts = {}) {
|
|
||||||
--| super(opts)
|
|
||||||
--| }
|
|
||||||
--|
|
|
||||||
--| async onPageCreated(page) {
|
|
||||||
--| page.sayHello = () => console.log('hello')
|
|
||||||
--| }
|
|
||||||
--| }
|
|
||||||
--|
|
|
||||||
--| /** @type {() => DebugPlugin} */
|
|
||||||
--| export const makeDebugPlugin = () => new DebugPlugin()
|
|
||||||
--|
|
|
||||||
--| /** @type {(_1: DebugPlugin) => (_2: PuppeteerExtra) => () => PuppeteerExtra} */
|
|
||||||
--| export const registerDebugPlugin = dp => p => () => p.use(dp)
|
|
||||||
--|
|
|
||||||
--| /** @type {(_1: PuppeteerExtra) => (_2: DebugPluginPage) => () => void} */
|
|
||||||
--| export const sayHello = () => page => () => page.sayHello()
|
|
||||||
--| ```
|
|
||||||
--|
|
|
||||||
--| `src/DebugPlugin.purs`
|
|
||||||
--| ```purescript
|
|
||||||
--| module DebugPlugin where
|
|
||||||
--|
|
|
||||||
--| import Prelude
|
|
||||||
--| import Effect (Effect)
|
|
||||||
--| import Effect.Class (class MonadEffect)
|
|
||||||
--| import Puppeteer (class Plugin, Puppeteer)
|
|
||||||
--| import Puppeteer.Page (Page)
|
|
||||||
--|
|
|
||||||
--| foreign import data DebugPlugin :: Type
|
|
||||||
--|
|
|
||||||
--| foreign import makeDebugPlugin :: Effect DebugPlugin
|
|
||||||
--|
|
|
||||||
--| foreign import registerDebugPlugin :: forall (r :: Row Type)
|
|
||||||
--| . Puppeteer r
|
|
||||||
--| -> Effect (Puppeteer (debugPlugin :: DebugPlugin | r))
|
|
||||||
--|
|
|
||||||
--| -- Note:
|
|
||||||
--| -- The puppeteer instance used here must have been
|
|
||||||
--| -- registered with `DebugPlugin`'s `use` in order to
|
|
||||||
--| -- invoke `sayHello`
|
|
||||||
--| foreign import sayHello :: forall (r :: Row Type)
|
|
||||||
--| . Puppeteer (debugPlugin :: DebugPlugin | r)
|
|
||||||
--| -> Page
|
|
||||||
--| -> Effect Unit
|
|
||||||
--|
|
|
||||||
--| instance debugPlugin :: Plugin DebugPlugin (debugPlugin :: DebugPlugin) where
|
|
||||||
--| use pptr _ = liftEffect $ registerDebugPlugin pptr
|
|
||||||
--| ```
|
|
||||||
class Plugin p (r :: Row Type) | p -> r where
|
|
||||||
--| Register a given puppeteer instance with plugin `p`
|
|
||||||
--|
|
|
||||||
--| The row type `r` should be used in that plugin's purescript
|
|
||||||
--| API to ensure the puppeteer instance used has had that
|
|
||||||
--| plugin registered.
|
|
||||||
use :: forall b c. Union r b c => Puppeteer r -> p -> Aff (Puppeteer c)
|
|
||||||
|
|
||||||
--| [`Browser`](https://pptr.dev/api/puppeteer.browser)
|
--| [`Browser`](https://pptr.dev/api/puppeteer.browser)
|
||||||
foreign import data Browser :: Type
|
foreign import data Browser :: Type
|
||||||
|
|
||||||
instance browserForeign :: ReadForeign Browser where
|
instance ReadForeign Browser where
|
||||||
readImpl = pure <<< unsafeFromForeign
|
readImpl = pure <<< unsafeFromForeign
|
||||||
|
|
||||||
--| [`Page`](https://pptr.dev/api/puppeteer.page)
|
--| [`Page`](https://pptr.dev/api/puppeteer.page)
|
||||||
foreign import data Page :: Type
|
foreign import data Page :: Type
|
||||||
|
|
||||||
instance pageForeign :: ReadForeign Page where
|
instance ReadForeign Page where
|
||||||
readImpl = pure <<< unsafeFromForeign
|
readImpl = pure <<< unsafeFromForeign
|
||||||
|
|
||||||
--| [`Frame`](https://pptr.dev/api/puppeteer.frame)
|
--| [`Frame`](https://pptr.dev/api/puppeteer.frame)
|
||||||
foreign import data Frame :: Type
|
foreign import data Frame :: Type
|
||||||
|
|
||||||
instance frameForeign :: ReadForeign Frame where
|
instance ReadForeign Frame where
|
||||||
readImpl = pure <<< unsafeFromForeign
|
readImpl = pure <<< unsafeFromForeign
|
||||||
|
|
||||||
--| [`BrowserContext`](https://pptr.dev/api/puppeteer.browsercontext)
|
--| [`BrowserContext`](https://pptr.dev/api/puppeteer.browsercontext)
|
||||||
foreign import data BrowserContext :: Type
|
foreign import data BrowserContext :: Type
|
||||||
|
|
||||||
instance browserContextForeign :: ReadForeign BrowserContext where
|
instance ReadForeign BrowserContext where
|
||||||
readImpl = pure <<< unsafeFromForeign
|
readImpl = pure <<< unsafeFromForeign
|
||||||
|
|
||||||
--| Represents both [`JSHandle`](https://pptr.dev/api/puppeteer.jshandle) & [`ElementHandle`](https://pptr.dev/api/puppeteer.elementhandle)
|
--| Represents both [`JSHandle`](https://pptr.dev/api/puppeteer.jshandle) & [`ElementHandle`](https://pptr.dev/api/puppeteer.elementhandle)
|
||||||
@ -174,100 +129,103 @@ foreign import data Handle :: Type -> Type
|
|||||||
--| [`Keyboard`](https://pptr.dev/api/puppeteer.keyboard)
|
--| [`Keyboard`](https://pptr.dev/api/puppeteer.keyboard)
|
||||||
foreign import data Keyboard :: Type
|
foreign import data Keyboard :: Type
|
||||||
|
|
||||||
|
instance ReadForeign Keyboard where
|
||||||
|
readImpl = pure <<< unsafeFromForeign
|
||||||
|
|
||||||
foreign import data Request :: Type
|
foreign import data Request :: Type
|
||||||
|
|
||||||
instance foreignRequest :: ReadForeign Request where
|
instance ReadForeign Request where
|
||||||
readImpl = pure <<< unsafeFromForeign
|
readImpl = pure <<< unsafeFromForeign
|
||||||
|
|
||||||
foreign import data Response :: Type
|
foreign import data Response :: Type
|
||||||
|
|
||||||
instance foreignResponse :: ReadForeign Response where
|
instance ReadForeign Response where
|
||||||
readImpl = pure <<< unsafeFromForeign
|
readImpl = pure <<< unsafeFromForeign
|
||||||
|
|
||||||
--| `Browser` or `BrowserContext`
|
--| `Browser` or `BrowserContext`
|
||||||
class PageProducer :: Type -> Constraint
|
class PageProducer :: Type -> Constraint
|
||||||
class PageProducer a
|
class PageProducer a
|
||||||
|
|
||||||
instance bpp :: PageProducer Browser
|
instance PageProducer Browser
|
||||||
instance bcpp :: PageProducer BrowserContext
|
instance PageProducer BrowserContext
|
||||||
|
|
||||||
--| `Page` or `Handle`
|
--| `Page` or `Handle`
|
||||||
class EvalTarget :: Type -> Constraint
|
class EvalTarget :: Type -> Constraint
|
||||||
class EvalTarget a
|
class EvalTarget a
|
||||||
|
|
||||||
instance pet :: EvalTarget Page
|
instance EvalTarget Page
|
||||||
instance het :: EvalTarget (Handle a)
|
instance EvalTarget (Handle a)
|
||||||
|
|
||||||
--| `Page` or `BrowserContext`
|
--| `Page` or `BrowserContext`
|
||||||
class BrowserAccess :: Type -> Constraint
|
class BrowserAccess :: Type -> Constraint
|
||||||
class BrowserAccess a
|
class BrowserAccess a
|
||||||
|
|
||||||
instance pba :: BrowserAccess Browser
|
instance BrowserAccess Browser
|
||||||
instance bcba :: BrowserAccess BrowserContext
|
instance BrowserAccess BrowserContext
|
||||||
|
|
||||||
class IsElement :: Type -> Constraint
|
class IsElement :: Type -> Constraint
|
||||||
class IsElement e
|
class IsElement e
|
||||||
|
|
||||||
instance anchorIsElement :: IsElement HTML.HTMLAnchorElement
|
instance IsElement HTML.HTMLAnchorElement
|
||||||
instance areaIsElement :: IsElement HTML.HTMLAreaElement
|
instance IsElement HTML.HTMLAreaElement
|
||||||
instance audioIsElement :: IsElement HTML.HTMLAudioElement
|
instance IsElement HTML.HTMLAudioElement
|
||||||
instance bRIsElement :: IsElement HTML.HTMLBRElement
|
instance IsElement HTML.HTMLBRElement
|
||||||
instance baseIsElement :: IsElement HTML.HTMLBaseElement
|
instance IsElement HTML.HTMLBaseElement
|
||||||
instance bodyIsElement :: IsElement HTML.HTMLBodyElement
|
instance IsElement HTML.HTMLBodyElement
|
||||||
instance buttonIsElement :: IsElement HTML.HTMLButtonElement
|
instance IsElement HTML.HTMLButtonElement
|
||||||
instance canvasIsElement :: IsElement HTML.HTMLCanvasElement
|
instance IsElement HTML.HTMLCanvasElement
|
||||||
instance dListIsElement :: IsElement HTML.HTMLDListElement
|
instance IsElement HTML.HTMLDListElement
|
||||||
instance dataIsElement :: IsElement HTML.HTMLDataElement
|
instance IsElement HTML.HTMLDataElement
|
||||||
instance dataListIsElement :: IsElement HTML.HTMLDataListElement
|
instance IsElement HTML.HTMLDataListElement
|
||||||
instance divIsElement :: IsElement HTML.HTMLDivElement
|
instance IsElement HTML.HTMLDivElement
|
||||||
instance document :: IsElement HTML.HTMLDocument
|
instance IsElement HTML.HTMLDocument
|
||||||
instance element :: IsElement HTML.HTMLElement
|
instance IsElement HTML.HTMLElement
|
||||||
instance embedIsElement :: IsElement HTML.HTMLEmbedElement
|
instance IsElement HTML.HTMLEmbedElement
|
||||||
instance fieldSetIsElement :: IsElement HTML.HTMLFieldSetElement
|
instance IsElement HTML.HTMLFieldSetElement
|
||||||
instance formIsElement :: IsElement HTML.HTMLFormElement
|
instance IsElement HTML.HTMLFormElement
|
||||||
instance hRIsElement :: IsElement HTML.HTMLHRElement
|
instance IsElement HTML.HTMLHRElement
|
||||||
instance headIsElement :: IsElement HTML.HTMLHeadElement
|
instance IsElement HTML.HTMLHeadElement
|
||||||
instance headingIsElement :: IsElement HTML.HTMLHeadingElement
|
instance IsElement HTML.HTMLHeadingElement
|
||||||
instance iFrameIsElement :: IsElement HTML.HTMLIFrameElement
|
instance IsElement HTML.HTMLIFrameElement
|
||||||
instance imageIsElement :: IsElement HTML.HTMLImageElement
|
instance IsElement HTML.HTMLImageElement
|
||||||
instance inputIsElement :: IsElement HTML.HTMLInputElement
|
instance IsElement HTML.HTMLInputElement
|
||||||
instance keygenIsElement :: IsElement HTML.HTMLKeygenElement
|
instance IsElement HTML.HTMLKeygenElement
|
||||||
instance lIIsElement :: IsElement HTML.HTMLLIElement
|
instance IsElement HTML.HTMLLIElement
|
||||||
instance labelIsElement :: IsElement HTML.HTMLLabelElement
|
instance IsElement HTML.HTMLLabelElement
|
||||||
instance legendIsElement :: IsElement HTML.HTMLLegendElement
|
instance IsElement HTML.HTMLLegendElement
|
||||||
instance linkIsElement :: IsElement HTML.HTMLLinkElement
|
instance IsElement HTML.HTMLLinkElement
|
||||||
instance mapIsElement :: IsElement HTML.HTMLMapElement
|
instance IsElement HTML.HTMLMapElement
|
||||||
instance mediaIsElement :: IsElement HTML.HTMLMediaElement
|
instance IsElement HTML.HTMLMediaElement
|
||||||
instance metaIsElement :: IsElement HTML.HTMLMetaElement
|
instance IsElement HTML.HTMLMetaElement
|
||||||
instance meterIsElement :: IsElement HTML.HTMLMeterElement
|
instance IsElement HTML.HTMLMeterElement
|
||||||
instance modIsElement :: IsElement HTML.HTMLModElement
|
instance IsElement HTML.HTMLModElement
|
||||||
instance oListIsElement :: IsElement HTML.HTMLOListElement
|
instance IsElement HTML.HTMLOListElement
|
||||||
instance objectIsElement :: IsElement HTML.HTMLObjectElement
|
instance IsElement HTML.HTMLObjectElement
|
||||||
instance optGroupIsElement :: IsElement HTML.HTMLOptGroupElement
|
instance IsElement HTML.HTMLOptGroupElement
|
||||||
instance optionIsElement :: IsElement HTML.HTMLOptionElement
|
instance IsElement HTML.HTMLOptionElement
|
||||||
instance outputIsElement :: IsElement HTML.HTMLOutputElement
|
instance IsElement HTML.HTMLOutputElement
|
||||||
instance paragraphIsElement :: IsElement HTML.HTMLParagraphElement
|
instance IsElement HTML.HTMLParagraphElement
|
||||||
instance paramIsElement :: IsElement HTML.HTMLParamElement
|
instance IsElement HTML.HTMLParamElement
|
||||||
instance preIsElement :: IsElement HTML.HTMLPreElement
|
instance IsElement HTML.HTMLPreElement
|
||||||
instance progressIsElement :: IsElement HTML.HTMLProgressElement
|
instance IsElement HTML.HTMLProgressElement
|
||||||
instance quoteIsElement :: IsElement HTML.HTMLQuoteElement
|
instance IsElement HTML.HTMLQuoteElement
|
||||||
instance scriptIsElement :: IsElement HTML.HTMLScriptElement
|
instance IsElement HTML.HTMLScriptElement
|
||||||
instance selectIsElement :: IsElement HTML.HTMLSelectElement
|
instance IsElement HTML.HTMLSelectElement
|
||||||
instance sourceIsElement :: IsElement HTML.HTMLSourceElement
|
instance IsElement HTML.HTMLSourceElement
|
||||||
instance spanIsElement :: IsElement HTML.HTMLSpanElement
|
instance IsElement HTML.HTMLSpanElement
|
||||||
instance styleIsElement :: IsElement HTML.HTMLStyleElement
|
instance IsElement HTML.HTMLStyleElement
|
||||||
instance tableCaptionIsElement :: IsElement HTML.HTMLTableCaptionElement
|
instance IsElement HTML.HTMLTableCaptionElement
|
||||||
instance tableCellIsElement :: IsElement HTML.HTMLTableCellElement
|
instance IsElement HTML.HTMLTableCellElement
|
||||||
instance tableColIsElement :: IsElement HTML.HTMLTableColElement
|
instance IsElement HTML.HTMLTableColElement
|
||||||
instance tableDataCellIsElement :: IsElement HTML.HTMLTableDataCellElement
|
instance IsElement HTML.HTMLTableDataCellElement
|
||||||
instance tableIsElement :: IsElement HTML.HTMLTableElement
|
instance IsElement HTML.HTMLTableElement
|
||||||
instance tableHeaderCellIsElement :: IsElement HTML.HTMLTableHeaderCellElement
|
instance IsElement HTML.HTMLTableHeaderCellElement
|
||||||
instance tableRowIsElement :: IsElement HTML.HTMLTableRowElement
|
instance IsElement HTML.HTMLTableRowElement
|
||||||
instance tableSectionIsElement :: IsElement HTML.HTMLTableSectionElement
|
instance IsElement HTML.HTMLTableSectionElement
|
||||||
instance templateIsElement :: IsElement HTML.HTMLTemplateElement
|
instance IsElement HTML.HTMLTemplateElement
|
||||||
instance textAreaIsElement :: IsElement HTML.HTMLTextAreaElement
|
instance IsElement HTML.HTMLTextAreaElement
|
||||||
instance timeIsElement :: IsElement HTML.HTMLTimeElement
|
instance IsElement HTML.HTMLTimeElement
|
||||||
instance titleIsElement :: IsElement HTML.HTMLTitleElement
|
instance IsElement HTML.HTMLTitleElement
|
||||||
instance trackIsElement :: IsElement HTML.HTMLTrackElement
|
instance IsElement HTML.HTMLTrackElement
|
||||||
instance uListIsElement :: IsElement HTML.HTMLUListElement
|
instance IsElement HTML.HTMLUListElement
|
||||||
instance videoIsElement :: IsElement HTML.HTMLVideoElement
|
instance IsElement HTML.HTMLVideoElement
|
||||||
|
@ -3,10 +3,12 @@ module Puppeteer.Browser
|
|||||||
, Product(..)
|
, Product(..)
|
||||||
, ChromeReleaseChannel(..)
|
, ChromeReleaseChannel(..)
|
||||||
, Connect
|
, Connect
|
||||||
|
, duplexConnect
|
||||||
|
, duplexProduct
|
||||||
|
, duplexChromeReleaseChannel
|
||||||
, disconnect
|
, disconnect
|
||||||
, websocketEndpoint
|
, websocketEndpoint
|
||||||
, connected
|
, connected
|
||||||
, prepareConnectOptions
|
|
||||||
, get
|
, get
|
||||||
, close
|
, close
|
||||||
) where
|
) where
|
||||||
@ -15,63 +17,93 @@ import Prelude
|
|||||||
|
|
||||||
import Control.Promise (Promise)
|
import Control.Promise (Promise)
|
||||||
import Control.Promise as Promise
|
import Control.Promise as Promise
|
||||||
|
import Data.Either (Either(..))
|
||||||
import Data.Enum (fromEnum)
|
import Data.Enum (fromEnum)
|
||||||
|
import Data.Generic.Rep (class Generic)
|
||||||
import Data.Maybe (Maybe)
|
import Data.Maybe (Maybe)
|
||||||
import Data.Time (Millisecond)
|
import Data.Newtype (unwrap, wrap)
|
||||||
|
import Data.Show.Generic (genericShow)
|
||||||
|
import Data.Time.Duration (Milliseconds(..))
|
||||||
import Effect (Effect)
|
import Effect (Effect)
|
||||||
import Effect.Aff (Aff)
|
import Effect.Aff (Aff)
|
||||||
import Foreign (Foreign, unsafeToForeign)
|
import Foreign (Foreign, unsafeToForeign)
|
||||||
import Puppeteer.Base (Browser) as X
|
import Puppeteer.Base (Browser) as X
|
||||||
import Puppeteer.Base (class BrowserAccess, Browser, BrowserContext, Viewport)
|
import Puppeteer.Base (class BrowserAccess, Browser, BrowserContext, JsDuplex(..), Viewport, duplex)
|
||||||
import Puppeteer.FFI as FFI
|
import Puppeteer.FFI as FFI
|
||||||
|
import Record (modify)
|
||||||
import Simple.JSON (writeImpl)
|
import Simple.JSON (writeImpl)
|
||||||
|
import Type.Prelude (Proxy(..))
|
||||||
|
|
||||||
data Product
|
data Product
|
||||||
= Chrome
|
= Chrome
|
||||||
| Firefox
|
| Firefox
|
||||||
|
|
||||||
|
derive instance Generic Product _
|
||||||
|
derive instance Eq Product
|
||||||
|
instance Show Product where
|
||||||
|
show = genericShow
|
||||||
|
|
||||||
|
duplexProduct :: JsDuplex Product String
|
||||||
|
duplexProduct =
|
||||||
|
let
|
||||||
|
toString Chrome = "chrome"
|
||||||
|
toString Firefox = "firefox"
|
||||||
|
fromString "chrome" = pure Chrome
|
||||||
|
fromString "firefox" = pure Firefox
|
||||||
|
fromString o = Left $ "unknown browser product " <> o
|
||||||
|
in
|
||||||
|
duplex toString fromString
|
||||||
|
|
||||||
data ChromeReleaseChannel
|
data ChromeReleaseChannel
|
||||||
= ChromeStable
|
= ChromeStable
|
||||||
| ChromeBeta
|
| ChromeBeta
|
||||||
| ChromeCanary
|
| ChromeCanary
|
||||||
| ChromeDev
|
| ChromeDev
|
||||||
|
|
||||||
|
derive instance Generic ChromeReleaseChannel _
|
||||||
|
derive instance Eq ChromeReleaseChannel
|
||||||
|
instance Show ChromeReleaseChannel where
|
||||||
|
show = genericShow
|
||||||
|
|
||||||
|
duplexChromeReleaseChannel :: JsDuplex ChromeReleaseChannel String
|
||||||
|
duplexChromeReleaseChannel =
|
||||||
|
let
|
||||||
|
toString ChromeStable = "chrome"
|
||||||
|
toString ChromeBeta = "chrome-beta"
|
||||||
|
toString ChromeCanary = "chrome-canary"
|
||||||
|
toString ChromeDev = "chrome-dev"
|
||||||
|
fromString "chrome" = pure ChromeStable
|
||||||
|
fromString "chrome-beta" = pure ChromeBeta
|
||||||
|
fromString "chrome-canary" = pure ChromeCanary
|
||||||
|
fromString "chrome-dev" = pure ChromeDev
|
||||||
|
fromString o = Left $ "unknown chrome release channel " <> o
|
||||||
|
in
|
||||||
|
duplex toString fromString
|
||||||
|
|
||||||
type Connect =
|
type Connect =
|
||||||
{ defaultViewport :: Maybe Viewport
|
{ defaultViewport :: Maybe Viewport
|
||||||
, ignoreHTTPSErrors :: Maybe Boolean
|
, ignoreHTTPSErrors :: Maybe Boolean
|
||||||
, protocolTimeout :: Maybe Millisecond
|
, protocolTimeout :: Maybe Milliseconds
|
||||||
, slowMo :: Maybe Millisecond
|
, slowMo :: Maybe Milliseconds
|
||||||
}
|
}
|
||||||
|
|
||||||
prepareViewport :: Viewport -> Foreign
|
type ConnectRaw =
|
||||||
prepareViewport
|
{ defaultViewport :: Maybe Viewport
|
||||||
{ deviceScaleFactor
|
, ignoreHTTPSErrors :: Maybe Boolean
|
||||||
, hasTouch
|
, protocolTimeout :: Maybe Number
|
||||||
, height
|
, slowMo :: Maybe Number
|
||||||
, width
|
|
||||||
, isLandscape
|
|
||||||
, isMobile
|
|
||||||
} = writeImpl
|
|
||||||
{ deviceScaleFactor: FFI.maybeToUndefined deviceScaleFactor
|
|
||||||
, hasTouch: FFI.maybeToUndefined hasTouch
|
|
||||||
, height
|
|
||||||
, width
|
|
||||||
, isLandscape: FFI.maybeToUndefined isLandscape
|
|
||||||
, isMobile: FFI.maybeToUndefined isMobile
|
|
||||||
}
|
}
|
||||||
|
|
||||||
prepareConnectOptions :: Connect -> Foreign
|
duplexConnect :: JsDuplex Connect ConnectRaw
|
||||||
prepareConnectOptions
|
duplexConnect =
|
||||||
{ defaultViewport
|
let
|
||||||
, ignoreHTTPSErrors
|
into r = modify (Proxy :: Proxy "protocolTimeout") (map unwrap)
|
||||||
, protocolTimeout
|
$ modify (Proxy :: Proxy "slowMo") (map unwrap) r
|
||||||
, slowMo
|
from r = pure
|
||||||
} = writeImpl
|
$ modify (Proxy :: Proxy "protocolTimeout") (map wrap)
|
||||||
{ defaultViewport: FFI.maybeToUndefined $ map prepareViewport defaultViewport
|
$ modify (Proxy :: Proxy "slowMo") (map wrap) r
|
||||||
, ignoreHTTPSErrors: FFI.maybeToUndefined ignoreHTTPSErrors
|
in
|
||||||
, protocolTimeout: FFI.maybeToUndefined $ map fromEnum protocolTimeout
|
duplex into from
|
||||||
, slowMo: FFI.maybeToUndefined $ map fromEnum slowMo
|
|
||||||
}
|
|
||||||
|
|
||||||
foreign import _close :: Browser -> Promise Unit
|
foreign import _close :: Browser -> Promise Unit
|
||||||
foreign import _get :: Foreign -> Effect Browser
|
foreign import _get :: Foreign -> Effect Browser
|
||||||
|
@ -1,13 +1,16 @@
|
|||||||
import { Page } from 'puppeteer'
|
import { Page } from 'puppeteer'
|
||||||
|
|
||||||
/** @type {(ev: import('puppeteer').PuppeteerLifeCycleEvent) => (p: Page) => Promise<any>} */
|
/** @type {(ev: import('puppeteer').PuppeteerLifeCycleEvent) => (p: Page) => () => Promise<any>} */
|
||||||
export const _forward = ev => p => p.goForward({ timeout: 0, waitUntil: ev })
|
export const _forward = ev => p => () =>
|
||||||
|
p.goForward({ timeout: 0, waitUntil: ev })
|
||||||
|
|
||||||
/** @type {(ev: import('puppeteer').PuppeteerLifeCycleEvent) => (p: Page) => Promise<any>} */
|
/** @type {(ev: import('puppeteer').PuppeteerLifeCycleEvent) => (p: Page) => () => Promise<any>} */
|
||||||
export const _back = ev => p => p.goBack({ timeout: 0, waitUntil: ev })
|
export const _back = ev => p => () => p.goBack({ timeout: 0, waitUntil: ev })
|
||||||
|
|
||||||
/** @type {(url: string) => (ev: import('puppeteer').PuppeteerLifeCycleEvent) => (p: Page) => Promise<any>} */
|
/** @type {(url: string) => (ev: import('puppeteer').PuppeteerLifeCycleEvent) => (p: Page) => () => Promise<any>} */
|
||||||
export const _to = url => ev => p => p.goto(url, { timeout: 0, waitUntil: ev })
|
export const _to = url => ev => p => () =>
|
||||||
|
p.goto(url, { timeout: 0, waitUntil: ev })
|
||||||
|
|
||||||
/** @type {(ev: import('puppeteer').PuppeteerLifeCycleEvent) => (p: Page) => Promise<any>} */
|
/** @type {(ev: import('puppeteer').PuppeteerLifeCycleEvent) => (p: Page) => () => Promise<any>} */
|
||||||
export const _reload = ev => p => p.goForward({ timeout: 0, waitUntil: ev })
|
export const _reload = ev => p => () =>
|
||||||
|
p.goForward({ timeout: 0, waitUntil: ev })
|
||||||
|
@ -7,27 +7,28 @@ import Control.Promise as Promise
|
|||||||
import Data.Maybe (Maybe)
|
import Data.Maybe (Maybe)
|
||||||
import Data.Newtype (unwrap)
|
import Data.Newtype (unwrap)
|
||||||
import Data.Time.Duration (Milliseconds(..))
|
import Data.Time.Duration (Milliseconds(..))
|
||||||
|
import Effect (Effect)
|
||||||
import Effect.Aff (Aff)
|
import Effect.Aff (Aff)
|
||||||
import Foreign (Foreign)
|
import Foreign (Foreign)
|
||||||
import Puppeteer.Base (LifecycleEvent(..), Page, URL, prepareLifecycleEvent)
|
import Puppeteer.Base (LifecycleEvent(..), Page, URL, duplexLifecycleEvent, duplexWrite)
|
||||||
import Puppeteer.HTTP as HTTP
|
import Puppeteer.HTTP as HTTP
|
||||||
|
|
||||||
foreign import _forward :: Foreign -> Page -> Promise (Maybe HTTP.Response)
|
foreign import _forward :: Foreign -> Page -> Effect (Promise (Maybe HTTP.Response))
|
||||||
foreign import _back :: Foreign -> Page -> Promise (Maybe HTTP.Response)
|
foreign import _back :: Foreign -> Page -> Effect (Promise (Maybe HTTP.Response))
|
||||||
foreign import _reload :: Foreign -> Page -> Promise (Maybe HTTP.Response)
|
foreign import _reload :: Foreign -> Page -> Effect (Promise (Maybe HTTP.Response))
|
||||||
foreign import _to :: String -> Foreign -> Page -> Promise (Maybe HTTP.Response)
|
foreign import _to :: String -> Foreign -> Page -> Effect (Promise (Maybe HTTP.Response))
|
||||||
|
|
||||||
forward :: LifecycleEvent -> Page -> Aff (Maybe HTTP.Response)
|
forward :: LifecycleEvent -> Page -> Aff (Maybe HTTP.Response)
|
||||||
forward ev = Promise.toAff <<< _forward (prepareLifecycleEvent ev)
|
forward ev = Promise.toAffE <<< _forward (duplexWrite duplexLifecycleEvent ev)
|
||||||
|
|
||||||
back :: LifecycleEvent -> Page -> Aff (Maybe HTTP.Response)
|
back :: LifecycleEvent -> Page -> Aff (Maybe HTTP.Response)
|
||||||
back ev = Promise.toAff <<< _back (prepareLifecycleEvent ev)
|
back ev = Promise.toAffE <<< _back (duplexWrite duplexLifecycleEvent ev)
|
||||||
|
|
||||||
to :: URL -> LifecycleEvent -> Page -> Aff (Maybe HTTP.Response)
|
to :: LifecycleEvent -> Page -> URL -> Aff (Maybe HTTP.Response)
|
||||||
to url ev = Promise.toAff <<< _to url (prepareLifecycleEvent ev)
|
to ev p u = Promise.toAffE $ _to u (duplexWrite duplexLifecycleEvent ev) p
|
||||||
|
|
||||||
reload :: LifecycleEvent -> Page -> Aff (Maybe HTTP.Response)
|
reload :: LifecycleEvent -> Page -> Aff (Maybe HTTP.Response)
|
||||||
reload ev = Promise.toAff <<< _reload (prepareLifecycleEvent ev)
|
reload ev = Promise.toAffE <<< _reload (duplexWrite duplexLifecycleEvent ev)
|
||||||
|
|
||||||
forward_ :: Page -> Aff (Maybe HTTP.Response)
|
forward_ :: Page -> Aff (Maybe HTTP.Response)
|
||||||
forward_ = forward Load
|
forward_ = forward Load
|
||||||
@ -35,8 +36,8 @@ forward_ = forward Load
|
|||||||
back_ :: Page -> Aff (Maybe HTTP.Response)
|
back_ :: Page -> Aff (Maybe HTTP.Response)
|
||||||
back_ = back Load
|
back_ = back Load
|
||||||
|
|
||||||
to_ :: URL -> Page -> Aff (Maybe HTTP.Response)
|
to_ :: Page -> URL -> Aff (Maybe HTTP.Response)
|
||||||
to_ url = to url Load
|
to_ = to Load
|
||||||
|
|
||||||
reload_ :: Page -> Aff (Maybe HTTP.Response)
|
reload_ :: Page -> Aff (Maybe HTTP.Response)
|
||||||
reload_ = reload Load
|
reload_ = reload Load
|
||||||
|
@ -17,7 +17,7 @@ import Data.Time.Duration (Milliseconds(..))
|
|||||||
import Effect (Effect)
|
import Effect (Effect)
|
||||||
import Effect.Aff (Aff)
|
import Effect.Aff (Aff)
|
||||||
import Foreign (Foreign)
|
import Foreign (Foreign)
|
||||||
import Puppeteer.Base (Context(..), Handle, LifecycleEvent, Page, prepareLifecycleEvent)
|
import Puppeteer.Base (Context(..), Handle, LifecycleEvent, Page, duplexWrite, duplexLifecycleEvent)
|
||||||
import Puppeteer.Selector (class Selector, toCSS)
|
import Puppeteer.Selector (class Selector, toCSS)
|
||||||
|
|
||||||
newtype NetworkIdleFor = NetworkIdleFor Milliseconds
|
newtype NetworkIdleFor = NetworkIdleFor Milliseconds
|
||||||
@ -35,7 +35,7 @@ foreign import _selectorToBeHidden :: String -> Page -> Promise Unit
|
|||||||
|
|
||||||
navigation :: LifecycleEvent -> Page -> Effect (Context WaitingForNavigationHint)
|
navigation :: LifecycleEvent -> Page -> Effect (Context WaitingForNavigationHint)
|
||||||
navigation ev p = do
|
navigation ev p = do
|
||||||
promise <- _navigation (prepareLifecycleEvent ev) p
|
promise <- _navigation (duplexWrite duplexLifecycleEvent ev) p
|
||||||
pure $ Context (\_ -> Promise.toAff $ promise)
|
pure $ Context (\_ -> Promise.toAff $ promise)
|
||||||
|
|
||||||
networkIdle :: NetworkIdleFor -> Page -> Aff Unit
|
networkIdle :: NetworkIdleFor -> Page -> Aff Unit
|
||||||
|
@ -34,14 +34,12 @@ import Control.Promise as Promise
|
|||||||
import Data.Array as Array
|
import Data.Array as Array
|
||||||
import Data.Either (hush)
|
import Data.Either (hush)
|
||||||
import Data.Maybe (Maybe)
|
import Data.Maybe (Maybe)
|
||||||
import Data.Nullable (Nullable)
|
|
||||||
import Data.Nullable as Nullable
|
|
||||||
import Effect (Effect)
|
import Effect (Effect)
|
||||||
import Effect.Aff (Aff)
|
import Effect.Aff (Aff)
|
||||||
import Foreign (Foreign, unsafeToForeign)
|
import Foreign (Foreign, unsafeToForeign)
|
||||||
import Node.Path (FilePath)
|
import Node.Path (FilePath)
|
||||||
import Puppeteer.Base (Page) as X
|
import Puppeteer.Base (Page) as X
|
||||||
import Puppeteer.Base (class PageProducer, Handle, Keyboard, LifecycleEvent, Page, URL, Viewport, prepareLifecycleEvent, prepareViewport)
|
import Puppeteer.Base (class PageProducer, Handle, Keyboard, LifecycleEvent, Page, URL, Viewport, duplexLifecycleEvent, duplexViewport, duplexWrite)
|
||||||
import Puppeteer.Handle (unsafeCoerceHandle)
|
import Puppeteer.Handle (unsafeCoerceHandle)
|
||||||
import Puppeteer.Selector (class Selector, toCSS)
|
import Puppeteer.Selector (class Selector, toCSS)
|
||||||
import Simple.JSON (readImpl, undefined, writeImpl)
|
import Simple.JSON (readImpl, undefined, writeImpl)
|
||||||
@ -147,10 +145,10 @@ content :: Page -> Aff String
|
|||||||
content = Promise.toAff <<< _content
|
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)
|
setContent s ev = Promise.toAff <<< _setContent s (duplexWrite duplexLifecycleEvent ev)
|
||||||
|
|
||||||
setViewport :: Viewport -> Page -> Aff Unit
|
setViewport :: Viewport -> Page -> Aff Unit
|
||||||
setViewport vp = Promise.toAff <<< _setViewport (prepareViewport vp)
|
setViewport vp = Promise.toAff <<< _setViewport (duplexWrite duplexViewport vp)
|
||||||
|
|
||||||
title :: Page -> Aff String
|
title :: Page -> Aff String
|
||||||
title = Promise.toAff <<< _title
|
title = Promise.toAff <<< _title
|
||||||
|
20
src/Puppeteer.Plugin.AdBlock.js
Normal file
20
src/Puppeteer.Plugin.AdBlock.js
Normal file
@ -0,0 +1,20 @@
|
|||||||
|
import AdBlock, {
|
||||||
|
PuppeteerExtraPluginAdblocker,
|
||||||
|
} from 'puppeteer-extra-plugin-adblocker'
|
||||||
|
import { PuppeteerExtra } from 'puppeteer-extra'
|
||||||
|
|
||||||
|
/** @type {(_: import('puppeteer-extra-plugin-adblocker').PluginOptions) => (_: PuppeteerExtra) => () => PuppeteerExtra} */
|
||||||
|
export const _install = o => p => () => p.use(AdBlock(o))
|
||||||
|
|
||||||
|
/** @type {(_: PuppeteerExtra) => () => Promise<import('@cliqz/adblocker-puppeteer').PuppeteerBlocker>} */
|
||||||
|
export const _blocker = p => () => {
|
||||||
|
const adblock = p.plugins.find(
|
||||||
|
pl => pl instanceof PuppeteerExtraPluginAdblocker,
|
||||||
|
)
|
||||||
|
|
||||||
|
if (!adblock || !(adblock instanceof PuppeteerExtraPluginAdblocker)) {
|
||||||
|
throw new Error('Adblock plugin not registered')
|
||||||
|
} else {
|
||||||
|
return adblock.getBlocker()
|
||||||
|
}
|
||||||
|
}
|
92
src/Puppeteer.Plugin.AdBlock.purs
Normal file
92
src/Puppeteer.Plugin.AdBlock.purs
Normal file
@ -0,0 +1,92 @@
|
|||||||
|
module Puppeteer.Plugin.AdBlock
|
||||||
|
( AdBlockMode(..)
|
||||||
|
, AdBlockOptions
|
||||||
|
, AdBlockPlugin
|
||||||
|
, AdBlocker
|
||||||
|
, install
|
||||||
|
, defaultOptions
|
||||||
|
, blocker
|
||||||
|
, cspInjectedH
|
||||||
|
, htmlFilteredH
|
||||||
|
, requestAllowedH
|
||||||
|
, requestBlockedH
|
||||||
|
, requestRedirectedH
|
||||||
|
, requestWhitelistedH
|
||||||
|
, scriptInjectedH
|
||||||
|
, styleInjectedH
|
||||||
|
) where
|
||||||
|
|
||||||
|
import Prelude
|
||||||
|
|
||||||
|
import Control.Promise (Promise)
|
||||||
|
import Control.Promise as Promise
|
||||||
|
import Data.Maybe (Maybe(..))
|
||||||
|
import Effect (Effect)
|
||||||
|
import Effect.Aff (Aff)
|
||||||
|
import Foreign (Foreign)
|
||||||
|
import Node.EventEmitter (EventEmitter)
|
||||||
|
import Node.EventEmitter as EventEmitter
|
||||||
|
import Node.EventEmitter.UtilTypes (EventHandle0) as EventEmitter
|
||||||
|
import Puppeteer.Base (Puppeteer)
|
||||||
|
import Puppeteer.FFI as FFI
|
||||||
|
import Simple.JSON (writeImpl)
|
||||||
|
|
||||||
|
-- | https://github.com/berstend/puppeteer-extra/tree/master/packages/puppeteer-extra-plugin-adblocker
|
||||||
|
foreign import data AdBlockPlugin :: Type
|
||||||
|
foreign import data AdBlocker :: Type
|
||||||
|
|
||||||
|
-- | https://github.com/berstend/puppeteer-extra/tree/master/packages/puppeteer-extra-plugin-adblocker#options
|
||||||
|
data AdBlockMode
|
||||||
|
-- | Block ads, but not trackers
|
||||||
|
= BlockAds
|
||||||
|
-- | Block ads & trackers
|
||||||
|
| BlockTrackers
|
||||||
|
-- | Block ads, trackers & annoyances
|
||||||
|
| BlockAnnoyances
|
||||||
|
|
||||||
|
type AdBlockOptions = { mode :: AdBlockMode, useDiskCache :: Boolean, cacheDir :: Maybe String }
|
||||||
|
|
||||||
|
defaultOptions :: AdBlockOptions
|
||||||
|
defaultOptions = { mode: BlockAds, useDiskCache: true, cacheDir: Nothing }
|
||||||
|
|
||||||
|
prepareOptions :: AdBlockOptions -> Foreign
|
||||||
|
prepareOptions { mode, useDiskCache, cacheDir } = FFI.mergeRecords
|
||||||
|
[ writeImpl case mode of
|
||||||
|
BlockAds -> { blockTrackers: false, blockTrackersAndAnnoyances: false }
|
||||||
|
BlockTrackers -> { blockTrackers: true, blockTrackersAndAnnoyances: false }
|
||||||
|
BlockAnnoyances -> { blockTrackers: true, blockTrackersAndAnnoyances: true }
|
||||||
|
, writeImpl { useCache: useDiskCache, cacheDir: FFI.maybeToUndefined cacheDir }
|
||||||
|
]
|
||||||
|
|
||||||
|
foreign import _install :: forall (r :: Row Type). Foreign -> Puppeteer r -> Effect (Puppeteer (adblock :: AdBlockPlugin | r))
|
||||||
|
foreign import _blocker :: forall (r :: Row Type). Puppeteer r -> Effect (Promise AdBlocker)
|
||||||
|
|
||||||
|
install :: forall (r :: Row Type). AdBlockOptions -> Puppeteer r -> Effect (Puppeteer (adblock :: AdBlockPlugin | r))
|
||||||
|
install o p = _install (prepareOptions o) p
|
||||||
|
|
||||||
|
blocker :: forall (r :: Row Type). Puppeteer (adblock :: AdBlockPlugin | r) -> Aff AdBlocker
|
||||||
|
blocker = Promise.toAffE <<< _blocker
|
||||||
|
|
||||||
|
cspInjectedH :: EventEmitter.EventHandle0 AdBlocker
|
||||||
|
cspInjectedH = EventEmitter.EventHandle "csp-injected" identity
|
||||||
|
|
||||||
|
htmlFilteredH :: EventEmitter.EventHandle0 AdBlocker
|
||||||
|
htmlFilteredH = EventEmitter.EventHandle "html-filtered" identity
|
||||||
|
|
||||||
|
requestAllowedH :: EventEmitter.EventHandle0 AdBlocker
|
||||||
|
requestAllowedH = EventEmitter.EventHandle "request-allowed" identity
|
||||||
|
|
||||||
|
requestBlockedH :: EventEmitter.EventHandle0 AdBlocker
|
||||||
|
requestBlockedH = EventEmitter.EventHandle "request-blocked" identity
|
||||||
|
|
||||||
|
requestRedirectedH :: EventEmitter.EventHandle0 AdBlocker
|
||||||
|
requestRedirectedH = EventEmitter.EventHandle "request-redirected" identity
|
||||||
|
|
||||||
|
requestWhitelistedH :: EventEmitter.EventHandle0 AdBlocker
|
||||||
|
requestWhitelistedH = EventEmitter.EventHandle "request-whitelisted" identity
|
||||||
|
|
||||||
|
scriptInjectedH :: EventEmitter.EventHandle0 AdBlocker
|
||||||
|
scriptInjectedH = EventEmitter.EventHandle "script-injected" identity
|
||||||
|
|
||||||
|
styleInjectedH :: EventEmitter.EventHandle0 AdBlocker
|
||||||
|
styleInjectedH = EventEmitter.EventHandle "style-injected" identity
|
5
src/Puppeteer.Plugin.AnonymousUserAgent.js
Normal file
5
src/Puppeteer.Plugin.AnonymousUserAgent.js
Normal file
@ -0,0 +1,5 @@
|
|||||||
|
import AnonUA from 'puppeteer-extra-plugin-anonymize-ua'
|
||||||
|
import { PuppeteerExtra } from 'puppeteer-extra'
|
||||||
|
|
||||||
|
/** @type {(_: PuppeteerExtra) => () => PuppeteerExtra} */
|
||||||
|
export const install = p => () => p.use(AnonUA())
|
8
src/Puppeteer.Plugin.AnonymousUserAgent.purs
Normal file
8
src/Puppeteer.Plugin.AnonymousUserAgent.purs
Normal file
@ -0,0 +1,8 @@
|
|||||||
|
module Puppeteer.Plugin.AnonymousUserAgent where
|
||||||
|
|
||||||
|
import Effect (Effect)
|
||||||
|
import Puppeteer.Base (Puppeteer)
|
||||||
|
|
||||||
|
-- | https://github.com/berstend/puppeteer-extra/tree/master/packages/puppeteer-extra-plugin-anonymize-ua
|
||||||
|
foreign import data AnonymousUserAgentPlugin :: Type
|
||||||
|
foreign import install :: forall (r :: Row Type). Puppeteer r -> Effect (Puppeteer (userAgent :: AnonymousUserAgentPlugin | r))
|
23
src/Puppeteer.Plugin.Captcha.js
Normal file
23
src/Puppeteer.Plugin.Captcha.js
Normal file
@ -0,0 +1,23 @@
|
|||||||
|
import { PuppeteerExtra } from 'puppeteer-extra'
|
||||||
|
import Captcha from 'puppeteer-extra-plugin-recaptcha'
|
||||||
|
|
||||||
|
/** @typedef {import('puppeteer-extra-plugin-recaptcha/dist/types').PluginOptions} PluginOptions */
|
||||||
|
/** @typedef {import('puppeteer-extra-plugin-recaptcha/dist/types').CaptchaInfo} CaptchaInfo */
|
||||||
|
/** @typedef {import('puppeteer-extra-plugin-recaptcha/dist/types').CaptchaSolution} CaptchaSolution */
|
||||||
|
/** @typedef {import('puppeteer-extra-plugin-recaptcha/dist/types').CaptchaSolved} CaptchaSolved */
|
||||||
|
/** @typedef {import('puppeteer').Page & import('puppeteer-extra-plugin-recaptcha/dist/types').RecaptchaPluginPageAdditions} Page */
|
||||||
|
|
||||||
|
/** @type {(_: PluginOptions) => (_: PuppeteerExtra) => () => PuppeteerExtra} */
|
||||||
|
export const _captcha = o => p => () => p.use(Captcha(o))
|
||||||
|
|
||||||
|
/** @type {(_: Page) => Promise<{captchas: CaptchaInfo[], filtered: unknown[]}>} */
|
||||||
|
export const _findCaptchas = p => p.findRecaptchas()
|
||||||
|
|
||||||
|
/** @type {(_: Page) => (_: CaptchaInfo[]) => Promise<{solutions: CaptchaSolution[]}>} */
|
||||||
|
export const _getSolutions = p => cs => p.getRecaptchaSolutions(cs)
|
||||||
|
|
||||||
|
/** @type {(_: Page) => (_: CaptchaSolution[]) => Promise<{solved: CaptchaSolved[]}>} */
|
||||||
|
export const _enterSolutions = p => cs => p.enterRecaptchaSolutions(cs)
|
||||||
|
|
||||||
|
/** @type {(_: Page) => Promise<{captchas: CaptchaInfo[], filtered: unknown[], solutions: CaptchaSolution[], solved: CaptchaSolved[]}>} */
|
||||||
|
export const _solveCaptchas = p => p.solveRecaptchas()
|
308
src/Puppeteer.Plugin.Captcha.purs
Normal file
308
src/Puppeteer.Plugin.Captcha.purs
Normal file
@ -0,0 +1,308 @@
|
|||||||
|
module Puppeteer.Plugin.Captcha
|
||||||
|
( install
|
||||||
|
, findCaptchas
|
||||||
|
, solveCaptchas
|
||||||
|
, defaultOptions
|
||||||
|
, CaptchaCallback(..)
|
||||||
|
, Options
|
||||||
|
, CaptchaProvider(..)
|
||||||
|
, CaptchaVendor(..)
|
||||||
|
, CaptchaPlugin
|
||||||
|
, CaptchaKind(..)
|
||||||
|
, CaptchaFiltered(..)
|
||||||
|
, Token2Captcha(..)
|
||||||
|
, CaptchaInfo
|
||||||
|
, CaptchaInfoMaybeFiltered
|
||||||
|
, CaptchaSolution
|
||||||
|
, CaptchaSolved
|
||||||
|
, CaptchaInfoDisplay
|
||||||
|
, SolveResult
|
||||||
|
, getSolutions
|
||||||
|
, enterSolutions
|
||||||
|
) where
|
||||||
|
|
||||||
|
import Prelude
|
||||||
|
|
||||||
|
import Control.Monad.Error.Class (liftEither)
|
||||||
|
import Control.Monad.Except (runExcept)
|
||||||
|
import Control.Promise (Promise)
|
||||||
|
import Control.Promise as Promise
|
||||||
|
import Data.Bifunctor (lmap)
|
||||||
|
import Data.Either (Either, hush)
|
||||||
|
import Data.Generic.Rep (class Generic)
|
||||||
|
import Data.JSDate (JSDate)
|
||||||
|
import Data.Maybe (Maybe(..))
|
||||||
|
import Data.Newtype (class Newtype, unwrap, wrap)
|
||||||
|
import Data.Show.Generic (genericShow)
|
||||||
|
import Data.Traversable (for, sequence)
|
||||||
|
import Data.Tuple (Tuple)
|
||||||
|
import Data.Tuple.Nested ((/\))
|
||||||
|
import Data.Variant (Variant)
|
||||||
|
import Effect (Effect)
|
||||||
|
import Effect.Aff (Aff)
|
||||||
|
import Effect.Exception (Error, error)
|
||||||
|
import Effect.Unsafe (unsafePerformEffect)
|
||||||
|
import Foreign (Foreign, unsafeFromForeign, unsafeReadTagged, unsafeToForeign)
|
||||||
|
import Puppeteer.Base (JsDuplex(..), Page, Puppeteer, duplex, duplexRead, duplexWrite)
|
||||||
|
import Puppeteer.FFI as FFI
|
||||||
|
import Record (modify, rename)
|
||||||
|
import Simple.JSON (class ReadForeign, class WriteForeign, readImpl, writeImpl)
|
||||||
|
import Type.Prelude (Proxy(..))
|
||||||
|
|
||||||
|
newtype CoerceDate = CoerceDate (Maybe JSDate)
|
||||||
|
|
||||||
|
derive instance Newtype CoerceDate _
|
||||||
|
|
||||||
|
instance ReadForeign CoerceDate where
|
||||||
|
readImpl f = pure $ CoerceDate $ hush $ runExcept $ unsafeReadTagged "Date" f
|
||||||
|
|
||||||
|
instance WriteForeign CoerceDate where
|
||||||
|
writeImpl (CoerceDate f) = unsafeToForeign f
|
||||||
|
|
||||||
|
newtype Token2Captcha = Token2Captcha String
|
||||||
|
|
||||||
|
derive instance Newtype Token2Captcha _
|
||||||
|
derive instance Generic Token2Captcha _
|
||||||
|
instance Show Token2Captcha where
|
||||||
|
show = genericShow
|
||||||
|
|
||||||
|
data CaptchaKind = KindCheckbox | KindInvisible | KindScore | KindOther String
|
||||||
|
|
||||||
|
derive instance Generic CaptchaKind _
|
||||||
|
derive instance Eq CaptchaKind
|
||||||
|
instance Show CaptchaKind where
|
||||||
|
show = genericShow
|
||||||
|
|
||||||
|
instance WriteForeign CaptchaKind where
|
||||||
|
writeImpl = writeImpl <<< case _ of
|
||||||
|
KindCheckbox -> "checkbox"
|
||||||
|
KindInvisible -> "invisible"
|
||||||
|
KindScore -> "score"
|
||||||
|
KindOther s -> s
|
||||||
|
|
||||||
|
instance ReadForeign CaptchaKind where
|
||||||
|
readImpl =
|
||||||
|
let
|
||||||
|
fromStr = case _ of
|
||||||
|
"checkbox" -> KindCheckbox
|
||||||
|
"invisible" -> KindInvisible
|
||||||
|
"score" -> KindScore
|
||||||
|
s -> KindOther s
|
||||||
|
in
|
||||||
|
map fromStr <<< readImpl
|
||||||
|
|
||||||
|
data CaptchaVendor = VendorReCaptcha | VendorHCaptcha | VendorOther String
|
||||||
|
|
||||||
|
derive instance Generic CaptchaVendor _
|
||||||
|
derive instance Eq CaptchaVendor
|
||||||
|
instance Show CaptchaVendor where
|
||||||
|
show = genericShow
|
||||||
|
|
||||||
|
vendorFromString :: String -> CaptchaVendor
|
||||||
|
vendorFromString = case _ of
|
||||||
|
"recaptcha" -> VendorReCaptcha
|
||||||
|
"hcaptcha" -> VendorHCaptcha
|
||||||
|
s -> VendorOther s
|
||||||
|
|
||||||
|
instance ReadForeign CaptchaVendor where
|
||||||
|
readImpl f = vendorFromString <$> readImpl f
|
||||||
|
|
||||||
|
instance WriteForeign CaptchaVendor where
|
||||||
|
writeImpl VendorHCaptcha = writeImpl "hcaptcha"
|
||||||
|
writeImpl VendorReCaptcha = writeImpl "recaptcha"
|
||||||
|
writeImpl (VendorOther s) = writeImpl s
|
||||||
|
|
||||||
|
data CaptchaFiltered = FilteredScoreBased | FilteredNotInViewport | FilteredInactive
|
||||||
|
|
||||||
|
derive instance Generic CaptchaFiltered _
|
||||||
|
derive instance Eq CaptchaFiltered
|
||||||
|
instance Show CaptchaFiltered where
|
||||||
|
show = genericShow
|
||||||
|
|
||||||
|
newtype CaptchaCallback = CaptchaCallback Foreign
|
||||||
|
|
||||||
|
derive instance Newtype CaptchaCallback _
|
||||||
|
derive newtype instance WriteForeign CaptchaCallback
|
||||||
|
derive newtype instance ReadForeign CaptchaCallback
|
||||||
|
derive instance Generic CaptchaCallback _
|
||||||
|
instance Show CaptchaCallback where
|
||||||
|
show _ = "CaptchaCallback"
|
||||||
|
|
||||||
|
filteredFromString :: String -> Maybe CaptchaFiltered
|
||||||
|
filteredFromString = case _ of
|
||||||
|
"solveInViewportOnly" -> Just FilteredNotInViewport
|
||||||
|
"solveScoreBased" -> Just FilteredScoreBased
|
||||||
|
"solveInactiveChallenges" -> Just FilteredInactive
|
||||||
|
_ -> Nothing
|
||||||
|
|
||||||
|
type CaptchaInfoDisplay =
|
||||||
|
{ size :: Maybe Foreign
|
||||||
|
, theme :: Maybe String
|
||||||
|
, top :: Maybe Foreign
|
||||||
|
, left :: Maybe Foreign
|
||||||
|
, width :: Maybe Foreign
|
||||||
|
, height :: Maybe Foreign
|
||||||
|
}
|
||||||
|
|
||||||
|
type CaptchaInfoMaybeFiltered = Tuple CaptchaInfo (Maybe CaptchaFiltered)
|
||||||
|
|
||||||
|
type CaptchaSolution =
|
||||||
|
{ vendor :: Maybe CaptchaVendor
|
||||||
|
, id :: Maybe String
|
||||||
|
, text :: Maybe String
|
||||||
|
, hasSolution :: Boolean
|
||||||
|
, requestAt :: Maybe JSDate
|
||||||
|
, responseAt :: Maybe JSDate
|
||||||
|
, duration :: Maybe Number
|
||||||
|
, provider :: Maybe String
|
||||||
|
, providerCaptchaId :: Maybe String
|
||||||
|
}
|
||||||
|
|
||||||
|
type CaptchaSolved =
|
||||||
|
{ vendor :: Maybe CaptchaVendor
|
||||||
|
, id :: Maybe String
|
||||||
|
, isSolved :: Maybe Boolean
|
||||||
|
, responseElement :: Maybe Boolean
|
||||||
|
, responseCallback :: Maybe Boolean
|
||||||
|
, solvedAt :: Maybe JSDate
|
||||||
|
}
|
||||||
|
|
||||||
|
duplexSolved :: JsDuplex CaptchaSolved _
|
||||||
|
duplexSolved =
|
||||||
|
let
|
||||||
|
toRaw r = modify (Proxy :: Proxy "solvedAt") CoerceDate
|
||||||
|
$ r
|
||||||
|
fromRaw r = pure
|
||||||
|
$ modify (Proxy :: Proxy "solvedAt") unwrap
|
||||||
|
$ r
|
||||||
|
in
|
||||||
|
duplex toRaw fromRaw
|
||||||
|
|
||||||
|
type SolveResult =
|
||||||
|
{ captchas :: Array CaptchaInfoMaybeFiltered
|
||||||
|
, solved :: Array CaptchaSolved
|
||||||
|
, solutions :: Array CaptchaSolution
|
||||||
|
}
|
||||||
|
|
||||||
|
data CaptchaProvider
|
||||||
|
= Provider2Captcha Token2Captcha
|
||||||
|
| ProviderCustom (Array CaptchaInfo -> Aff (Array CaptchaSolution))
|
||||||
|
|
||||||
|
prepareCustomProvider :: (Array CaptchaInfo -> Aff (Array CaptchaSolution)) -> Array CaptchaInfo -> Promise { solutions :: Array CaptchaSolution }
|
||||||
|
prepareCustomProvider f = unsafePerformEffect <<< Promise.fromAff <<< map (\solutions -> { solutions }) <<< f
|
||||||
|
|
||||||
|
type Options =
|
||||||
|
{ visualize :: Maybe Boolean
|
||||||
|
, skipNotInViewport :: Maybe Boolean
|
||||||
|
, skipScoreBased :: Maybe Boolean
|
||||||
|
, skipInactive :: Maybe Boolean
|
||||||
|
, provider :: CaptchaProvider
|
||||||
|
}
|
||||||
|
|
||||||
|
defaultOptions :: Token2Captcha -> Options
|
||||||
|
defaultOptions token = { visualize: Nothing, skipNotInViewport: Nothing, skipInactive: Nothing, skipScoreBased: Nothing, provider: Provider2Captcha token }
|
||||||
|
|
||||||
|
prepareOptions :: Options -> Foreign
|
||||||
|
prepareOptions { provider, visualize, skipInactive, skipNotInViewport, skipScoreBased } =
|
||||||
|
writeImpl
|
||||||
|
{ provider: case provider of
|
||||||
|
Provider2Captcha (Token2Captcha t) -> writeImpl { id: "2captcha", token: t }
|
||||||
|
ProviderCustom f -> writeImpl { fn: unsafeToForeign $ prepareCustomProvider f }
|
||||||
|
, visualFeedback: FFI.maybeToUndefined visualize
|
||||||
|
, solveInViewportOnly: FFI.maybeToUndefined $ skipNotInViewport
|
||||||
|
, solveScoreBased: FFI.maybeToUndefined $ not <$> skipScoreBased
|
||||||
|
, solveInactiveChallenges: FFI.maybeToUndefined $ not <$> skipInactive
|
||||||
|
, throwOnError: true
|
||||||
|
}
|
||||||
|
|
||||||
|
type CaptchaInfo =
|
||||||
|
{ kind :: Maybe CaptchaKind
|
||||||
|
, vendor :: Maybe CaptchaVendor
|
||||||
|
, id :: Maybe String
|
||||||
|
, sitekey :: Maybe String
|
||||||
|
, s :: Maybe String
|
||||||
|
, isInViewport :: Maybe Boolean
|
||||||
|
, isInvisible :: Maybe Boolean
|
||||||
|
, hasActiveChallengePopup :: Maybe Boolean
|
||||||
|
, hasChallengeFrame :: Maybe Boolean
|
||||||
|
, action :: Maybe String
|
||||||
|
, callback :: CaptchaCallback
|
||||||
|
, hasResponseElement :: Maybe Boolean
|
||||||
|
, url :: Maybe String
|
||||||
|
, display :: Maybe CaptchaInfoDisplay
|
||||||
|
}
|
||||||
|
|
||||||
|
duplexSoln :: JsDuplex CaptchaSolution _
|
||||||
|
duplexSoln =
|
||||||
|
let
|
||||||
|
toRaw r = modify (Proxy :: Proxy "requestAt") CoerceDate
|
||||||
|
$ modify (Proxy :: Proxy "responseAt") CoerceDate
|
||||||
|
$ r
|
||||||
|
fromRaw r = pure
|
||||||
|
$ modify (Proxy :: Proxy "requestAt") (unwrap)
|
||||||
|
$ modify (Proxy :: Proxy "responseAt") (unwrap)
|
||||||
|
$ r
|
||||||
|
in
|
||||||
|
duplex toRaw fromRaw
|
||||||
|
|
||||||
|
duplexInfo :: JsDuplex CaptchaInfo _
|
||||||
|
duplexInfo =
|
||||||
|
let
|
||||||
|
toRaw r = rename (Proxy :: Proxy "kind") (Proxy :: Proxy "_type") $ r
|
||||||
|
fromRaw r = pure $ rename (Proxy :: Proxy "_type") (Proxy :: Proxy "kind") r
|
||||||
|
in
|
||||||
|
duplex toRaw fromRaw
|
||||||
|
|
||||||
|
foreign import data CaptchaPlugin :: Type
|
||||||
|
|
||||||
|
foreign import _captcha :: forall (r :: Row Type). Foreign -> Puppeteer r -> Effect (Puppeteer (captcha :: CaptchaPlugin | r))
|
||||||
|
foreign import _findCaptchas :: Page -> Promise Foreign
|
||||||
|
foreign import _getSolutions :: Page -> Foreign -> Promise Foreign
|
||||||
|
foreign import _enterSolutions :: Page -> Foreign -> Promise Foreign
|
||||||
|
foreign import _solveCaptchas :: Page -> Promise Foreign
|
||||||
|
|
||||||
|
read :: forall @a. ReadForeign a => Foreign -> Either Error a
|
||||||
|
read = lmap (error <<< show) <<< runExcept <<< readImpl
|
||||||
|
|
||||||
|
install :: forall (r :: Row Type). Options -> Puppeteer r -> Effect (Puppeteer (captcha :: CaptchaPlugin | r))
|
||||||
|
install o p = _captcha (prepareOptions o) p
|
||||||
|
|
||||||
|
infos :: Foreign -> Either Error (Array CaptchaInfoMaybeFiltered)
|
||||||
|
infos f = do
|
||||||
|
{ captchas, filtered } <- read @({ captchas :: Array Foreign, filtered :: Array Foreign }) f
|
||||||
|
captchas' <- sequence $ duplexRead duplexInfo <$> captchas
|
||||||
|
let captchas'' = (_ /\ Nothing) <$> captchas'
|
||||||
|
filtered' <- for filtered \f' -> do
|
||||||
|
c <- duplexRead duplexInfo f'
|
||||||
|
{ filtered: wasF, filteredReason } <- read @({ filtered :: Boolean, filteredReason :: String }) f'
|
||||||
|
pure $ case filteredFromString filteredReason of
|
||||||
|
Just r | wasF -> c /\ (Just r)
|
||||||
|
_ -> c /\ Nothing
|
||||||
|
pure $ captchas'' <> filtered'
|
||||||
|
|
||||||
|
findCaptchas :: forall (r :: Row Type). Puppeteer (captcha :: CaptchaPlugin | r) -> Page -> Aff (Array CaptchaInfoMaybeFiltered)
|
||||||
|
findCaptchas _ p = do
|
||||||
|
f <- Promise.toAff $ _findCaptchas p
|
||||||
|
liftEither $ infos f
|
||||||
|
|
||||||
|
getSolutions :: forall (r :: Row Type). Puppeteer (captcha :: CaptchaPlugin | r) -> Page -> Array CaptchaInfo -> Aff (Array CaptchaSolution)
|
||||||
|
getSolutions _ p is = do
|
||||||
|
f <- Promise.toAff $ _getSolutions p (writeImpl $ duplexWrite duplexInfo <$> is)
|
||||||
|
{ solutions } <- liftEither $ read @({ solutions :: Array Foreign }) f
|
||||||
|
liftEither $ for solutions $ duplexRead duplexSoln
|
||||||
|
|
||||||
|
enterSolutions :: forall (r :: Row Type). Puppeteer (captcha :: CaptchaPlugin | r) -> Page -> Array CaptchaSolution -> Aff (Array CaptchaSolved)
|
||||||
|
enterSolutions _ p sols = do
|
||||||
|
f <- Promise.toAff $ _enterSolutions p (writeImpl $ duplexWrite duplexSoln <$> sols)
|
||||||
|
{ solved } <- liftEither $ read @({ solved :: Array Foreign }) f
|
||||||
|
liftEither $ for solved $ duplexRead duplexSolved
|
||||||
|
|
||||||
|
solveCaptchas :: forall (r :: Row Type). Puppeteer (captcha :: CaptchaPlugin | r) -> Page -> Aff SolveResult
|
||||||
|
solveCaptchas _ p = do
|
||||||
|
f <- Promise.toAff $ _solveCaptchas p
|
||||||
|
{ solved, solutions } <- liftEither $ read @({ solved :: Array Foreign, solutions :: Array Foreign }) f
|
||||||
|
captchas <- liftEither $ infos f
|
||||||
|
liftEither do
|
||||||
|
solved' <- for solved $ duplexRead duplexSolved
|
||||||
|
solutions' <- for solutions $ duplexRead duplexSoln
|
||||||
|
pure $ { captchas, solved: solved', solutions: solutions' }
|
5
src/Puppeteer.Plugin.Stealth.js
Normal file
5
src/Puppeteer.Plugin.Stealth.js
Normal file
@ -0,0 +1,5 @@
|
|||||||
|
import { PuppeteerExtra } from 'puppeteer-extra'
|
||||||
|
import Stealth from 'puppeteer-extra-plugin-stealth'
|
||||||
|
|
||||||
|
/** @type {(_: PuppeteerExtra) => () => PuppeteerExtra} */
|
||||||
|
export const install = p => () => p.use(Stealth())
|
8
src/Puppeteer.Plugin.Stealth.purs
Normal file
8
src/Puppeteer.Plugin.Stealth.purs
Normal file
@ -0,0 +1,8 @@
|
|||||||
|
module Puppeteer.Plugin.Stealth where
|
||||||
|
|
||||||
|
import Effect (Effect)
|
||||||
|
import Puppeteer.Base (Puppeteer)
|
||||||
|
|
||||||
|
-- | https://github.com/berstend/puppeteer-extra/tree/master/packages/puppeteer-extra-plugin-stealth
|
||||||
|
foreign import data StealthPlugin :: Type
|
||||||
|
foreign import install :: forall (r :: Row Type). Puppeteer r -> Effect (Puppeteer (stealth :: StealthPlugin | r))
|
@ -1,6 +1,6 @@
|
|||||||
module Puppeteer
|
module Puppeteer
|
||||||
( module X
|
( module X
|
||||||
, puppeteer
|
, new
|
||||||
, connect
|
, connect
|
||||||
, launch
|
, launch
|
||||||
, connect_
|
, connect_
|
||||||
@ -23,12 +23,12 @@ import Effect (Effect)
|
|||||||
import Effect.Aff (Aff)
|
import Effect.Aff (Aff)
|
||||||
import Effect.Unsafe (unsafePerformEffect)
|
import Effect.Unsafe (unsafePerformEffect)
|
||||||
import Foreign (Foreign)
|
import Foreign (Foreign)
|
||||||
import Puppeteer.Base (Puppeteer)
|
import Puppeteer.Base (Puppeteer, duplexWrite)
|
||||||
import Puppeteer.Base as X
|
import Puppeteer.Base as X
|
||||||
import Puppeteer.Screenshot as X
|
|
||||||
import Puppeteer.Browser (Browser)
|
import Puppeteer.Browser (Browser)
|
||||||
import Puppeteer.Browser as Browser
|
import Puppeteer.Browser as Browser
|
||||||
import Puppeteer.FFI as FFI
|
import Puppeteer.FFI as FFI
|
||||||
|
import Puppeteer.Screenshot as X
|
||||||
import Simple.JSON (writeImpl)
|
import Simple.JSON (writeImpl)
|
||||||
|
|
||||||
--| [https://pptr.dev/api/puppeteer.puppeteerlaunchoptions]
|
--| [https://pptr.dev/api/puppeteer.puppeteerlaunchoptions]
|
||||||
@ -113,7 +113,7 @@ prepareConnectOptions
|
|||||||
, headers: FFI.maybeToUndefined $ map FFI.mapToRecord headers
|
, headers: FFI.maybeToUndefined $ map FFI.mapToRecord headers
|
||||||
, transport: FFI.maybeToUndefined $ map transport' transport
|
, transport: FFI.maybeToUndefined $ map transport' transport
|
||||||
}
|
}
|
||||||
, writeImpl $ map Browser.prepareConnectOptions browser
|
, writeImpl $ map (duplexWrite Browser.duplexConnect) browser
|
||||||
]
|
]
|
||||||
|
|
||||||
prepareLaunchOptions :: Launch -> Foreign
|
prepareLaunchOptions :: Launch -> Foreign
|
||||||
@ -157,7 +157,7 @@ prepareLaunchOptions
|
|||||||
, headless: if headless then writeImpl "new" else writeImpl false
|
, headless: if headless then writeImpl "new" else writeImpl false
|
||||||
, userDataDir: FFI.maybeToUndefined userDataDir
|
, userDataDir: FFI.maybeToUndefined userDataDir
|
||||||
}
|
}
|
||||||
, writeImpl $ FFI.maybeToUndefined $ map Browser.prepareConnectOptions browser
|
, writeImpl $ FFI.maybeToUndefined $ map (duplexWrite Browser.duplexConnect) browser
|
||||||
]
|
]
|
||||||
|
|
||||||
foreign import _puppeteer :: Effect (Promise (Puppeteer ()))
|
foreign import _puppeteer :: Effect (Promise (Puppeteer ()))
|
||||||
@ -168,8 +168,8 @@ foreign import _launch :: forall p. Foreign -> Puppeteer p -> Effect (Promise Br
|
|||||||
--|
|
--|
|
||||||
--| [`PuppeteerExtra`](https://github.com/berstend/puppeteer-extra/blob/master/packages/puppeteer-extra/src/index.ts)
|
--| [`PuppeteerExtra`](https://github.com/berstend/puppeteer-extra/blob/master/packages/puppeteer-extra/src/index.ts)
|
||||||
--| [`PuppeteerNode`](https://pptr.dev/api/puppeteer.puppeteernode)
|
--| [`PuppeteerNode`](https://pptr.dev/api/puppeteer.puppeteernode)
|
||||||
puppeteer :: Unit -> Aff (Puppeteer ())
|
new :: Aff (Puppeteer ())
|
||||||
puppeteer _ = Promise.toAffE _puppeteer
|
new = Promise.toAffE _puppeteer
|
||||||
|
|
||||||
--| Connect to an existing browser instance
|
--| Connect to an existing browser instance
|
||||||
--|
|
--|
|
||||||
|
@ -12,7 +12,7 @@ import Test.Spec.Assertions (shouldEqual, shouldNotEqual)
|
|||||||
import Test.Util (test, testE)
|
import Test.Util (test, testE)
|
||||||
|
|
||||||
spec :: SpecT Aff Unit Effect Unit
|
spec :: SpecT Aff Unit Effect Unit
|
||||||
spec = beforeAll (Pup.launch_ =<< Pup.puppeteer unit)
|
spec = beforeAll (Pup.launch_ =<< Pup.new)
|
||||||
$ describe "Browser" do
|
$ describe "Browser" do
|
||||||
testE "websocketEndpoint" $ shouldNotEqual "" <=< Pup.Browser.websocketEndpoint
|
testE "websocketEndpoint" $ shouldNotEqual "" <=< Pup.Browser.websocketEndpoint
|
||||||
testE "connected" $ shouldEqual true <=< Pup.Browser.connected
|
testE "connected" $ shouldEqual true <=< Pup.Browser.connected
|
||||||
@ -22,6 +22,6 @@ spec = beforeAll (Pup.launch_ =<< Pup.puppeteer unit)
|
|||||||
connected <- liftEffect $ Pup.Browser.connected b
|
connected <- liftEffect $ Pup.Browser.connected b
|
||||||
connected `shouldEqual` false
|
connected `shouldEqual` false
|
||||||
|
|
||||||
pup <- Pup.puppeteer unit
|
pup <- Pup.new
|
||||||
b' <- Pup.connect (Pup.connectDefault $ Pup.BrowserWebsocket ws) pup
|
b' <- Pup.connect (Pup.connectDefault $ Pup.BrowserWebsocket ws) pup
|
||||||
Pup.Browser.close b'
|
Pup.Browser.close b'
|
||||||
|
@ -100,7 +100,7 @@ withPage :: SpecT Aff Pup.Page Effect Unit -> SpecT Aff Unit Effect Unit
|
|||||||
withPage =
|
withPage =
|
||||||
let
|
let
|
||||||
withPage' spec' _ = do
|
withPage' spec' _ = do
|
||||||
pup <- Pup.puppeteer unit
|
pup <- Pup.new
|
||||||
b <- Pup.launch_ pup
|
b <- Pup.launch_ pup
|
||||||
page <- Pup.Page.new b
|
page <- Pup.Page.new b
|
||||||
failOnPageError page do
|
failOnPageError page do
|
||||||
|
@ -61,7 +61,7 @@ withPage =
|
|||||||
|
|
||||||
spec :: SpecT Aff Unit Effect Unit
|
spec :: SpecT Aff Unit Effect Unit
|
||||||
spec =
|
spec =
|
||||||
beforeAll (Pup.launch_ =<< Pup.puppeteer unit)
|
beforeAll (Pup.launch_ =<< Pup.new)
|
||||||
$ afterAll Pup.Browser.close
|
$ afterAll Pup.Browser.close
|
||||||
$ do
|
$ do
|
||||||
describe "Event" do
|
describe "Event" do
|
||||||
|
@ -75,7 +75,7 @@ inputPage =
|
|||||||
"""
|
"""
|
||||||
|
|
||||||
spec :: SpecT Aff Unit Effect Unit
|
spec :: SpecT Aff Unit Effect Unit
|
||||||
spec = beforeAll (Pup.launch_ =<< Pup.puppeteer unit)
|
spec = beforeAll (Pup.launch_ =<< Pup.new)
|
||||||
$ afterAll Pup.Browser.close
|
$ afterAll Pup.Browser.close
|
||||||
$ describe "Page" do
|
$ describe "Page" do
|
||||||
test "new, close, isClosed" \b -> do
|
test "new, close, isClosed" \b -> do
|
||||||
|
99
test/Puppeteer.Plugin.Spec.purs
Normal file
99
test/Puppeteer.Plugin.Spec.purs
Normal file
@ -0,0 +1,99 @@
|
|||||||
|
module Puppeteer.Plugin.Spec where
|
||||||
|
|
||||||
|
import Prelude
|
||||||
|
|
||||||
|
import Control.Monad.Error.Class (liftMaybe, try)
|
||||||
|
import Control.Monad.ST as ST
|
||||||
|
import Control.Monad.ST.Global as ST
|
||||||
|
import Control.Monad.ST.Ref as ST
|
||||||
|
import Control.Parallel (parallel, sequential)
|
||||||
|
import Data.Array as Array
|
||||||
|
import Data.Foldable (for_)
|
||||||
|
import Data.Maybe (Maybe(..))
|
||||||
|
import Data.Newtype (wrap)
|
||||||
|
import Data.Traversable (for)
|
||||||
|
import Effect (Effect)
|
||||||
|
import Effect.Aff (Aff, delay)
|
||||||
|
import Effect.Class (liftEffect)
|
||||||
|
import Effect.Console (warn)
|
||||||
|
import Effect.Exception (error)
|
||||||
|
import Node.EventEmitter as EventEmitter
|
||||||
|
import Node.Process as Process
|
||||||
|
import Puppeteer as Pup
|
||||||
|
import Puppeteer.Eval as Pup.Eval
|
||||||
|
import Puppeteer.Page as Pup.Page
|
||||||
|
import Puppeteer.Page.Navigate as Pup.Page.Nav
|
||||||
|
import Puppeteer.Page.WaitFor as Pup.Page.WaitFor
|
||||||
|
import Puppeteer.Plugin.AdBlock as Pup.AdBlock
|
||||||
|
import Puppeteer.Plugin.AnonymousUserAgent as Pup.AnonUA
|
||||||
|
import Puppeteer.Plugin.Captcha as Pup.Captcha
|
||||||
|
import Puppeteer.Plugin.Stealth as Pup.Stealth
|
||||||
|
import Test.Spec (SpecT(..), describe, focus, pending)
|
||||||
|
import Test.Spec.Assertions (shouldEqual, shouldSatisfy)
|
||||||
|
import Test.Util (test)
|
||||||
|
|
||||||
|
spec :: SpecT Aff Unit Effect Unit
|
||||||
|
spec = describe "Plugin" do
|
||||||
|
args <- liftEffect Process.argv
|
||||||
|
|
||||||
|
let
|
||||||
|
pendingUnlessArg a t b =
|
||||||
|
if not $ Array.any (_ == a) args then do
|
||||||
|
let msg = " (skipped unless `" <> a <> "`, ex. `spago test " <> a <> "`)"
|
||||||
|
pending (t <> msg)
|
||||||
|
else
|
||||||
|
test t b
|
||||||
|
|
||||||
|
describe "Captcha" do
|
||||||
|
test "install" do
|
||||||
|
pup <- Pup.new
|
||||||
|
pup' <- liftEffect $ Pup.Captcha.install (Pup.Captcha.defaultOptions $ wrap "") pup
|
||||||
|
void $ Pup.launch_ pup'
|
||||||
|
|
||||||
|
pendingUnlessArg "--test-captcha" "solves captchas" do
|
||||||
|
token <- liftMaybe (error "TWOCAPTCHA_API_KEY not present") <=< liftEffect <<< Process.lookupEnv $ "TWOCAPTCHA_API_KEY"
|
||||||
|
let
|
||||||
|
urls =
|
||||||
|
[ "https://www.google.com/recaptcha/api2/demo"
|
||||||
|
, "https://accounts.hcaptcha.com/demo"
|
||||||
|
, "https://democaptcha.com/demo-form-eng/hcaptcha.html"
|
||||||
|
]
|
||||||
|
pup <- Pup.new
|
||||||
|
pup' <- liftEffect $ Pup.Captcha.install (Pup.Captcha.defaultOptions $ wrap token) pup
|
||||||
|
b <- Pup.launch_ pup'
|
||||||
|
sequential $ for_ urls \u -> parallel do
|
||||||
|
p <- Pup.Page.new b
|
||||||
|
_ <- Pup.Page.Nav.to_ p u
|
||||||
|
{ solved } <- Pup.Captcha.solveCaptchas pup' p
|
||||||
|
Array.length solved `shouldSatisfy` (_ >= 1)
|
||||||
|
pure unit
|
||||||
|
describe "Adblock" do
|
||||||
|
test "install" do
|
||||||
|
pup <- Pup.new
|
||||||
|
pup' <- liftEffect $ Pup.AdBlock.install Pup.AdBlock.defaultOptions pup
|
||||||
|
void $ Pup.AdBlock.blocker pup'
|
||||||
|
pendingUnlessArg "--test-adblock" "blocks ads" do
|
||||||
|
pup <- Pup.new
|
||||||
|
pup' <- liftEffect $ Pup.AdBlock.install Pup.AdBlock.defaultOptions pup
|
||||||
|
blocker <- Pup.AdBlock.blocker pup'
|
||||||
|
requestsBlocked <- liftEffect $ ST.toEffect (ST.new 0)
|
||||||
|
stylesInjected <- liftEffect $ ST.toEffect (ST.new 0)
|
||||||
|
let add1On st h = liftEffect $ EventEmitter.on_ h (void $ ST.toEffect $ ST.modify (_ + 1) st) blocker
|
||||||
|
add1On requestsBlocked Pup.AdBlock.requestBlockedH
|
||||||
|
add1On stylesInjected Pup.AdBlock.styleInjectedH
|
||||||
|
b <- Pup.launch_ pup'
|
||||||
|
p <- Pup.Page.new b
|
||||||
|
_ <- Pup.Page.Nav.to_ p "https://www.google.com/search?q=rent%20a%20car"
|
||||||
|
Pup.Page.WaitFor.networkIdle (Pup.Page.WaitFor.NetworkIdleFor $ wrap 200.0) p
|
||||||
|
reqs <- liftEffect $ ST.toEffect $ ST.read requestsBlocked
|
||||||
|
stys <- liftEffect $ ST.toEffect $ ST.read stylesInjected
|
||||||
|
reqs `shouldSatisfy` (_ >= 1)
|
||||||
|
stys `shouldSatisfy` (_ >= 1)
|
||||||
|
describe "Stealth" do
|
||||||
|
test "install" do
|
||||||
|
pup <- Pup.new
|
||||||
|
void $ liftEffect $ Pup.Stealth.install pup
|
||||||
|
describe "AnonymousUserAgent" do
|
||||||
|
test "install" do
|
||||||
|
pup <- Pup.new
|
||||||
|
void $ liftEffect $ Pup.AnonUA.install pup
|
@ -11,6 +11,7 @@ import Puppeteer.Browser as Pup.Browser
|
|||||||
import Puppeteer.Browser.Spec as Spec.Browser
|
import Puppeteer.Browser.Spec as Spec.Browser
|
||||||
import Puppeteer.Handle.Spec as Spec.Handle
|
import Puppeteer.Handle.Spec as Spec.Handle
|
||||||
import Puppeteer.Page.Spec as Spec.Page
|
import Puppeteer.Page.Spec as Spec.Page
|
||||||
|
import Puppeteer.Plugin.Spec as Spec.Plugin
|
||||||
import Puppeteer.Selector.Spec as Spec.Selector
|
import Puppeteer.Selector.Spec as Spec.Selector
|
||||||
import Test.Spec (SpecT, describe, mapSpecTree)
|
import Test.Spec (SpecT, describe, mapSpecTree)
|
||||||
import Test.Spec.Assertions (shouldEqual)
|
import Test.Spec.Assertions (shouldEqual)
|
||||||
@ -19,11 +20,11 @@ import Test.Util (test)
|
|||||||
spec :: SpecT Aff Unit Effect Unit
|
spec :: SpecT Aff Unit Effect Unit
|
||||||
spec = describe "Puppeteer" do
|
spec = describe "Puppeteer" do
|
||||||
test "launch" do
|
test "launch" do
|
||||||
pup <- Pup.puppeteer unit
|
pup <- Pup.new
|
||||||
map void Pup.launch_ pup
|
map void Pup.launch_ pup
|
||||||
|
|
||||||
test "connect" do
|
test "connect" do
|
||||||
pup <- Pup.puppeteer unit
|
pup <- Pup.new
|
||||||
|
|
||||||
b1 <- Pup.launch_ pup
|
b1 <- Pup.launch_ pup
|
||||||
ws <- liftEffect $ Pup.Browser.websocketEndpoint b1
|
ws <- liftEffect $ Pup.Browser.websocketEndpoint b1
|
||||||
@ -39,4 +40,5 @@ spec = describe "Puppeteer" do
|
|||||||
Spec.Browser.spec
|
Spec.Browser.spec
|
||||||
Spec.Page.spec
|
Spec.Page.spec
|
||||||
Spec.Handle.spec
|
Spec.Handle.spec
|
||||||
|
Spec.Plugin.spec
|
||||||
mapSpecTree (pure <<< unwrap) identity Spec.Selector.spec
|
mapSpecTree (pure <<< unwrap) identity Spec.Selector.spec
|
||||||
|
@ -19,11 +19,13 @@ import Test.Spec.Config (defaultConfig)
|
|||||||
import Test.Spec.Reporter (consoleReporter)
|
import Test.Spec.Reporter (consoleReporter)
|
||||||
import Test.Spec.Result (Result(..))
|
import Test.Spec.Result (Result(..))
|
||||||
import Test.Spec.Runner (runSpecT)
|
import Test.Spec.Runner (runSpecT)
|
||||||
|
import Dotenv as Dotenv
|
||||||
|
|
||||||
foreign import errorString :: Error -> Effect String
|
foreign import errorString :: Error -> Effect String
|
||||||
|
|
||||||
main :: Effect Unit
|
main :: Effect Unit
|
||||||
main = launchAff_ do
|
main = launchAff_ do
|
||||||
|
Dotenv.loadFile
|
||||||
let cfg = defaultConfig { timeout = Nothing, exit = false }
|
let cfg = defaultConfig { timeout = Nothing, exit = false }
|
||||||
run <- liftEffect $ runSpecT cfg [ consoleReporter ] Spec.spec
|
run <- liftEffect $ runSpecT cfg [ consoleReporter ] Spec.spec
|
||||||
res <- (map (join <<< map (foldl Array.snoc [])) run) :: Aff (Array Result)
|
res <- (map (join <<< map (foldl Array.snoc [])) run) :: Aff (Array Result)
|
||||||
|
Loading…
Reference in New Issue
Block a user