From 63201148ac77fa71c050a9064e4ab8b0f7006df1 Mon Sep 17 00:00:00 2001 From: Tim Voronov Date: Wed, 17 Jul 2019 13:29:16 -0400 Subject: [PATCH] Added possibility to set custom viewport size (#334) * Added possibility to set custom viewport size * Fixed linting issue * Renamed ScreenSize to Viewport * Updated e2e test --- e2e/pages/dynamic/components/app.js | 5 ++ .../dynamic/components/pages/media/index.js | 26 ++++++ e2e/tests/dynamic/doc/viewport/size.fql | 16 ++++ pkg/drivers/cdp/driver.go | 11 ++- pkg/drivers/cdp/page.go | 54 +++++++++--- pkg/drivers/driver.go | 10 +-- pkg/drivers/http/driver.go | 2 +- pkg/drivers/params.go | 20 +++++ pkg/runtime/values/helpers.go | 4 +- pkg/stdlib/html/document.go | 88 ++++++++++++++++--- 10 files changed, 199 insertions(+), 37 deletions(-) create mode 100644 e2e/pages/dynamic/components/pages/media/index.js create mode 100644 e2e/tests/dynamic/doc/viewport/size.fql create mode 100644 pkg/drivers/params.go diff --git a/e2e/pages/dynamic/components/app.js b/e2e/pages/dynamic/components/app.js index 59fa0bb1..67665d2d 100644 --- a/e2e/pages/dynamic/components/app.js +++ b/e2e/pages/dynamic/components/app.js @@ -3,6 +3,7 @@ import IndexPage from './pages/index.js'; import FormsPage from './pages/forms/index.js'; import EventsPage from './pages/events/index.js'; import IframePage from './pages/iframes/index.js'; +import MediaPage from './pages/media/index.js'; const e = React.createElement; const Router = ReactRouter.Router; @@ -51,6 +52,10 @@ export default React.memo(function AppComponent(params = {}) { path: '/iframe', component: IframePage }), + e(Route, { + path: '/media', + component: MediaPage + }), ]), redirectTo ]) diff --git a/e2e/pages/dynamic/components/pages/media/index.js b/e2e/pages/dynamic/components/pages/media/index.js new file mode 100644 index 00000000..42e4e787 --- /dev/null +++ b/e2e/pages/dynamic/components/pages/media/index.js @@ -0,0 +1,26 @@ +const e = React.createElement; + +export default class MediaPage extends React.Component { + render() { + return e("div", { id: "media" }, [ + e("div", { className: "row" }, [ + e("div", {className: "col-6"}, [ + e("h3", null, [ + "Height" + ]), + e("span", { id: "screen-height"}, [ + window.innerHeight + ]), + ]), + e("div", {className: "col-6"}, [ + e("h3", null, [ + "Width" + ]), + e("span", { id: "screen-width"}, [ + window.innerWidth + ]) + ]) + ]) + ]) + } +} \ No newline at end of file diff --git a/e2e/tests/dynamic/doc/viewport/size.fql b/e2e/tests/dynamic/doc/viewport/size.fql new file mode 100644 index 00000000..1f3471c3 --- /dev/null +++ b/e2e/tests/dynamic/doc/viewport/size.fql @@ -0,0 +1,16 @@ +LET url = @dynamic + "?redirect=/media" +LET expectedW = 1920 +LET expectedH = 1080 + +LET doc = DOCUMENT(url, { + driver: 'cdp', + viewport: { + width: expectedW, + height: expectedH + } +}) + +LET actualW = TO_INT(INNER_TEXT(doc, '#screen-width')) +LET actualH = TO_INT(INNER_TEXT(doc, '#screen-height')) + +RETURN EXPECT(expectedW, actualW) + EXPECT(expectedH, actualH) \ No newline at end of file diff --git a/pkg/drivers/cdp/driver.go b/pkg/drivers/cdp/driver.go index 44be83e9..7e199417 100644 --- a/pkg/drivers/cdp/driver.go +++ b/pkg/drivers/cdp/driver.go @@ -20,6 +20,11 @@ const DriverName = "cdp" const BlankPageURL = "about:blank" const DefaultTimeout = 5000 * time.Millisecond +var defaultViewport = &drivers.Viewport{ + Width: 1600, + Height: 900, +} + type Driver struct { mu sync.Mutex dev *devtool.DevTools @@ -42,7 +47,7 @@ func (drv *Driver) Name() string { return drv.options.Name } -func (drv *Driver) Open(ctx context.Context, params drivers.OpenPageParams) (drivers.HTMLPage, error) { +func (drv *Driver) Open(ctx context.Context, params drivers.Params) (drivers.HTMLPage, error) { logger := logging.FromContext(ctx) err := drv.init(ctx) @@ -98,6 +103,10 @@ func (drv *Driver) Open(ctx context.Context, params drivers.OpenPageParams) (dri params.UserAgent = drv.options.UserAgent } + if params.Viewport == nil { + params.Viewport = defaultViewport + } + return LoadHTMLPage(ctx, conn, params) } diff --git a/pkg/drivers/cdp/page.go b/pkg/drivers/cdp/page.go index 2c23850c..a48e33ad 100644 --- a/pkg/drivers/cdp/page.go +++ b/pkg/drivers/cdp/page.go @@ -46,7 +46,7 @@ func handleLoadError(logger *zerolog.Logger, client *cdp.Client) { func LoadHTMLPage( ctx context.Context, conn *rpcc.Conn, - params drivers.OpenPageParams, + params drivers.Params, ) (*HTMLPage, error) { logger := logging.FromContext(ctx) @@ -56,11 +56,11 @@ func LoadHTMLPage( client := cdp.NewClient(conn) - err := runBatch( - func() error { - return client.Page.Enable(ctx) - }, + if err := client.Page.Enable(ctx); err != nil { + return nil, err + } + err := runBatch( func() error { return client.Page.SetLifecycleEventsEnabled( ctx, @@ -99,18 +99,50 @@ func LoadHTMLPage( func() error { return client.Network.Enable(ctx, network.NewEnableArgs()) }, + + func() error { + return client.Page.SetBypassCSP(ctx, page.NewSetBypassCSPArgs(true)) + }, + + func() error { + if params.Viewport == nil { + return nil + } + + orientation := emulation.ScreenOrientation{} + + if !params.Viewport.Landscape { + orientation.Type = "portraitPrimary" + orientation.Angle = 0 + } else { + orientation.Type = "landscapePrimary" + orientation.Angle = 90 + } + + scaleFactor := params.Viewport.ScaleFactor + + if scaleFactor <= 0 { + scaleFactor = 1 + } + + deviceArgs := emulation.NewSetDeviceMetricsOverrideArgs( + params.Viewport.Width, + params.Viewport.Height, + scaleFactor, + params.Viewport.Mobile, + ).SetScreenOrientation(orientation) + + return client.Emulation.SetDeviceMetricsOverride( + ctx, + deviceArgs, + ) + }, ) if err != nil { return nil, err } - err = client.Page.SetBypassCSP(ctx, page.NewSetBypassCSPArgs(true)) - - if err != nil { - return nil, err - } - if params.Cookies != nil { cookies := make([]network.CookieParam, 0, len(params.Cookies)) diff --git a/pkg/drivers/driver.go b/pkg/drivers/driver.go index edb57eae..1042e87b 100644 --- a/pkg/drivers/driver.go +++ b/pkg/drivers/driver.go @@ -18,18 +18,10 @@ type ( drivers map[string]Driver } - OpenPageParams struct { - URL string - UserAgent string - KeepCookies bool - Cookies []HTTPCookie - Header HTTPHeader - } - Driver interface { io.Closer Name() string - Open(ctx context.Context, params OpenPageParams) (HTMLPage, error) + Open(ctx context.Context, params Params) (HTMLPage, error) } ) diff --git a/pkg/drivers/http/driver.go b/pkg/drivers/http/driver.go index 38f3eea8..4fbefcc4 100644 --- a/pkg/drivers/http/driver.go +++ b/pkg/drivers/http/driver.go @@ -62,7 +62,7 @@ func (drv *Driver) Name() string { return DriverName } -func (drv *Driver) Open(ctx context.Context, params drivers.OpenPageParams) (drivers.HTMLPage, error) { +func (drv *Driver) Open(ctx context.Context, params drivers.Params) (drivers.HTMLPage, error) { req, err := http.NewRequest(http.MethodGet, params.URL, nil) if err != nil { diff --git a/pkg/drivers/params.go b/pkg/drivers/params.go new file mode 100644 index 00000000..c2496b1c --- /dev/null +++ b/pkg/drivers/params.go @@ -0,0 +1,20 @@ +package drivers + +type ( + Viewport struct { + Height int + Width int + ScaleFactor float64 + Mobile bool + Landscape bool + } + + Params struct { + URL string + UserAgent string + KeepCookies bool + Cookies []HTTPCookie + Header HTTPHeader + Viewport *Viewport + } +) diff --git a/pkg/runtime/values/helpers.go b/pkg/runtime/values/helpers.go index 3757052a..5af6d5a7 100644 --- a/pkg/runtime/values/helpers.go +++ b/pkg/runtime/values/helpers.go @@ -231,10 +231,10 @@ func Unmarshal(value json.RawMessage) (core.Value, error) { return Parse(o), nil } -func ToBoolean(input core.Value) core.Value { +func ToBoolean(input core.Value) Boolean { switch input.Type() { case types.Boolean: - return input + return input.(Boolean) case types.String: return NewBoolean(input.(String) != "") case types.Int: diff --git a/pkg/stdlib/html/document.go b/pkg/stdlib/html/document.go index 178a75e4..8ecd8e13 100644 --- a/pkg/stdlib/html/document.go +++ b/pkg/stdlib/html/document.go @@ -13,20 +13,23 @@ import ( ) type PageLoadParams struct { - drivers.OpenPageParams + drivers.Params Driver string Timeout time.Duration } // Open opens an HTML page by a given url. -// By default, loads a document by http call - resulted document does not support any interactions. -// If passed "true" as a second argument, headless browser is used for loading the document which support interactions. -// @param url (String) - Target url string. If passed "about:blank" for dynamic document - it will open an empty page. -// @param isDynamicOrParams (Boolean|PageLoadParams) - Either a boolean value that indicates whether to use dynamic page -// or an object with the following properties : -// dynamic (Boolean) - Optional, indicates whether to use dynamic page. -// timeout (Int) - Optional, Open load timeout. -// @returns (HTMLDocument) - Returns loaded HTML document. +// By default, loads a page by http call - resulted page does not support any interactions. +// @param params (Object) - Optional, An object containing the following properties : +// driver (String) - Optional, driver name. +// timeout (Int) - Optional, timeout. +// userAgent (String) - Optional, user agent. +// keepCookies (Boolean) - Optional, boolean value indicating whether to use cookies from previous sessions. +// i.e. not to open a page in the Incognito mode. +// cookies (HTTPCookie) - Optional, set of HTTP cookies. +// header (HTTPHeader) - Optional, HTTP headers. +// viewport (Viewport) - Optional, viewport params. +// @returns (HTMLPage) - Returns loaded HTML page. func Open(ctx context.Context, args ...core.Value) (core.Value, error) { err := core.ValidateArgs(args, 1, 2) @@ -65,12 +68,12 @@ func Open(ctx context.Context, args ...core.Value) (core.Value, error) { return values.None, err } - return drv.Open(ctx, params.OpenPageParams) + return drv.Open(ctx, params.Params) } func newDefaultDocLoadParams(url values.String) PageLoadParams { return PageLoadParams{ - OpenPageParams: drivers.OpenPageParams{ + Params: drivers.Params{ URL: url.String(), }, Timeout: time.Second * 30, @@ -155,9 +158,19 @@ func newPageLoadParams(url values.String, arg core.Value) (PageLoadParams, error res.Header = header } + viewport, exists := obj.Get(values.NewString("viewport")) + + if exists { + viewport, err := parseViewport(viewport) + + if err != nil { + return res, err + } + + res.Viewport = viewport + } case types.String: res.Driver = arg.(values.String).String() - case types.Boolean: b := arg.(values.Boolean) @@ -165,7 +178,6 @@ func newPageLoadParams(url values.String, arg core.Value) (PageLoadParams, error if b { res.Driver = cdp.DriverName } - } return res, nil @@ -291,3 +303,53 @@ func parseHeader(header *values.Object) drivers.HTTPHeader { return res } + +func parseViewport(value core.Value) (*drivers.Viewport, error) { + if err := core.ValidateType(value, types.Object); err != nil { + return nil, err + } + + res := &drivers.Viewport{} + + viewport := value.(*values.Object) + + width, exists := viewport.Get(values.NewString("width")) + + if exists { + if err := core.ValidateType(width, types.Int); err != nil { + return nil, err + } + + res.Width = int(values.ToInt(width)) + } + + height, exists := viewport.Get(values.NewString("height")) + + if exists { + if err := core.ValidateType(height, types.Int); err != nil { + return nil, err + } + + res.Height = int(values.ToInt(height)) + } + + mobile, exists := viewport.Get(values.NewString("mobile")) + + if exists { + res.Mobile = bool(values.ToBoolean(mobile)) + } + + landscape, exists := viewport.Get(values.NewString("landscape")) + + if exists { + res.Landscape = bool(values.ToBoolean(landscape)) + } + + scaleFactor, exists := viewport.Get(values.NewString("scaleFactor")) + + if exists { + res.ScaleFactor = float64(values.ToFloat(scaleFactor)) + } + + return res, nil +}