1
0
mirror of https://github.com/MontFerret/ferret.git synced 2025-01-26 03:51:57 +02:00

Integration tests (#170)

This commit is contained in:
Tim Voronov 2018-11-12 14:53:36 -05:00 committed by GitHub
parent 423d84cd07
commit de774ba03e
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
50 changed files with 695 additions and 60 deletions

View File

@ -1,5 +1,14 @@
## Changelog
### 0.5.0
#### Added
- DateTime functions.
#### Fixed
- Unable to define variables and make function calls before FILTER, SORT and etc statements.
- ``INNER_HTML`` returns outer HTML instead for dynamic elements.
- ``INNER_TEXT`` returns HTML instead from dynamic elements.
### 0.4.0
#### Added
- ``COLLECT`` keyword [#141](https://github.com/MontFerret/ferret/pull/141)

View File

@ -1,14 +1,14 @@
package main
import (
"context"
"flag"
"fmt"
"os"
"path/filepath"
"github.com/MontFerret/ferret/e2e/runner"
"github.com/MontFerret/ferret/e2e/server"
"github.com/rs/zerolog"
"os"
"path/filepath"
)
var (
@ -24,12 +24,6 @@ var (
"root directory with test pages",
)
port = flag.Uint64(
"port",
8080,
"server port",
)
cdp = flag.String(
"cdp",
"http://0.0.0.0:9222",
@ -42,17 +36,27 @@ func main() {
logger := zerolog.New(zerolog.ConsoleWriter{Out: os.Stderr})
s := server.New(server.Settings{
Port: *port,
Dir: *pagesDir,
staticPort := uint64(8080)
static := server.New(server.Settings{
Port: staticPort,
Dir: filepath.Join(*pagesDir, "static"),
})
ctx, cancel := context.WithCancel(context.Background())
defer cancel()
dynamicPort := uint64(8081)
dynamic := server.New(server.Settings{
Port: dynamicPort,
Dir: filepath.Join(*pagesDir, "dynamic"),
})
go func() {
if err := s.Start(); err != nil {
logger.Info().Timestamp().Msg("shutting down the server")
if err := static.Start(); err != nil {
logger.Info().Timestamp().Msg("shutting down the static pages server")
}
}()
go func() {
if err := dynamic.Start(); err != nil {
logger.Info().Timestamp().Msg("shutting down the dynamic pages server")
}
}()
@ -71,18 +75,17 @@ func main() {
}
r := runner.New(logger, runner.Settings{
ServerAddress: fmt.Sprintf("http://0.0.0.0:%d", *port),
CDPAddress: *cdp,
Dir: *testsDir,
StaticServerAddress: fmt.Sprintf("http://0.0.0.0:%d", staticPort),
DynamicServerAddress: fmt.Sprintf("http://0.0.0.0:%d", dynamicPort),
CDPAddress: *cdp,
Dir: *testsDir,
})
err := r.Run()
if err := s.Stop(ctx); err != nil {
logger.Fatal().Timestamp().Err(err).Msg("failed to stop server")
}
if err != nil {
os.Exit(1)
}
os.Exit(0)
}

View File

@ -0,0 +1,29 @@
import Layout from './layout.js';
import IndexPage from './pages/index.js';
import FormsPage from './pages/forms/index.js';
const e = React.createElement;
const Router = ReactRouter.Router;
const Switch = ReactRouter.Switch;
const Route = ReactRouter.Route;
const Redirect = ReactRouter.Redirect;
const createBrowserHistory = History.createBrowserHistory;
export default function AppComponent({ redirect = null}) {
return e(Router, { history: createBrowserHistory() },
e(Layout, null, [
e(Switch, null, [
e(Route, {
path: '/',
exact: true,
component: IndexPage
}),
e(Route, {
path: '/forms',
component: FormsPage
})
]),
redirect ? e(Redirect, { to: redirect }) : null
])
)
}

View File

@ -0,0 +1,27 @@
const e = React.createElement;
const NavLink = ReactRouterDOM.NavLink;
export default function Layout({ children }) {
return e("div", { id: "layout"}, [
e("nav", { className: "navbar navbar-expand-md navbar-dark bg-dark mb-4" }, [
e(NavLink, { className: "navbar-brand", to: "/"}, "Ferret"),
e("button", { className: "navbar-toggler", type: "button"}, [
e("span", { className: "navbar-toggler-icon" })
]),
e("div", { className: "collapse navbar-collapse" }, [
e("ul", { className: "navbar-nav mr-auto" }, [
e("li", { className: "nav-item"}, [
e(NavLink, { className: "nav-link", to: "/forms" }, "Forms")
]),
e("li", { className: "nav-item"}, [
e(NavLink, { className: "nav-link", to: "/navigation" }, "Navigation")
]),
e("li", { className: "nav-item"}, [
e(NavLink, { className: "nav-link", to: "/events" }, "Events")
])
])
])
]),
e("main", { className: "container"}, children)
])
}

View File

@ -0,0 +1,124 @@
const e = React.createElement;
export default class FormsPage extends React.Component {
constructor(props) {
super(props);
this.state = {
textInput: "",
select: "",
multiSelect: "",
textarea: ""
};
this.handleTextInput = (evt) => {
evt.preventDefault();
this.setState({
textInput: evt.target.value
});
};
this.handleSelect = (evt) => {
evt.preventDefault();
this.setState({
select: evt.target.value
});
};
this.handleMultiSelect = (evt) => {
evt.preventDefault();
this.setState({
multiSelect: Array.prototype.map.call(evt.target.selectedOptions, i => i.value).join(", ")
});
};
this.handleTtextarea = (evt) => {
evt.preventDefault();
this.setState({
textarea: evt.target.value
});
}
}
render() {
return e("form", null, [
e("div", { className: "form-group" }, [
e("label", null, "Text input"),
e("input", {
id: "text_input",
type: "text",
className: "form-control",
onChange: this.handleTextInput
}),
e("small", {
id: "text_output",
className: "form-text text-muted"
},
this.state.textInput
)
]),
e("div", { className: "form-group" }, [
e("label", null, "Select"),
e("select", {
id: "select_input",
className: "form-control",
onChange: this.handleSelect
},
[
e("option", null, 1),
e("option", null, 2),
e("option", null, 3),
e("option", null, 4),
e("option", null, 5),
]
),
e("small", {
id: "select_output",
className: "form-text text-muted"
}, this.state.select
)
]),
e("div", { className: "form-group" }, [
e("label", null, "Multi select"),
e("select", {
id: "multi_select_input",
multiple: true,
className: "form-control",
onChange: this.handleMultiSelect
},
[
e("option", null, 1),
e("option", null, 2),
e("option", null, 3),
e("option", null, 4),
e("option", null, 5),
]
),
e("small", {
id: "multi_select_output",
className: "form-text text-muted"
}, this.state.multiSelect
)
]),
e("div", { className: "form-group" }, [
e("label", null, "Textarea"),
e("textarea", {
id: "textarea_input",
rows:"5",
className: "form-control",
onChange: this.handleTtextarea
}
),
e("small", {
id: "textarea_output",
className: "form-text text-muted"
}, this.state.textarea
)
]),
])
}
}

View File

@ -0,0 +1,8 @@
const e = React.createElement;
export default function IndexPage() {
return e("div", { className: "jumbotron" }, [
e("div", null, e("h1", null, "Welcome to Ferret E2E test page!")),
e("div", null, e("p", { className: "lead" }, "It has several pages for testing different possibilities of the library"))
])
}

View File

@ -0,0 +1,4 @@
/* Show it's not fixed to the top */
body {
min-height: 75rem;
}

View File

@ -0,0 +1,21 @@
<!doctype html>
<html lang="en">
<head>
<meta charset="utf-8">
<meta http-equiv="x-ua-compatible" content="ie=edge">
<title>Ferret E2E SPA</title>
<meta name="description" content="">
<meta name="viewport" content="width=device-width, initial-scale=1, shrink-to-fit=no">
<link rel="stylesheet" href="https://stackpath.bootstrapcdn.com/bootstrap/4.1.3/css/bootstrap.min.css" integrity="sha384-MCw98/SFnGE8fJT3GXwEOngsV7Zt27NXFoaoApmYm81iuXoPkFOJwJ8ERdknLPMO" crossorigin="anonymous">
<link rel="stylesheet" href="index.css">
</head>
<body class="text-center">
<div id="root"></div>
<script src="https://unpkg.com/react@16.6.1/umd/react.production.min.js"></script>
<script src="https://unpkg.com/react-dom@16.6.1/umd/react-dom.production.min.js"></script>
<script src="https://unpkg.com/history@4.7.2/umd/history.min.js"></script>
<script src="https://unpkg.com/react-router@4.3.1/umd/react-router.js"></script>
<script src="https://unpkg.com/react-router-dom@4.3.1/umd/react-router-dom.js"></script>
<script src="index.js" type="module"></script>
</body>
</html>

View File

@ -0,0 +1,9 @@
import AppComponent from "./components/app.js";
import { parse } from "./utils/qs.js";
const qs = parse(location.search);
ReactDOM.render(
React.createElement(AppComponent, qs),
document.getElementById("root")
);

View File

@ -0,0 +1,82 @@
'use strict';
var has = Object.prototype.hasOwnProperty
, undef;
/**
* Decode a URI encoded string.
*
* @param {String} input The URI encoded string.
* @returns {String} The decoded string.
* @api private
*/
function decode(input) {
return decodeURIComponent(input.replace(/\+/g, ' '));
}
/**
* Simple query string parser.
*
* @param {String} query The query string that needs to be parsed.
* @returns {Object}
* @api public
*/
export function parse(query) {
var parser = /([^=?&]+)=?([^&]*)/g
, result = {}
, part;
while (part = parser.exec(query)) {
var key = decode(part[1])
, value = decode(part[2]);
//
// Prevent overriding of existing properties. This ensures that build-in
// methods like `toString` or __proto__ are not overriden by malicious
// querystrings.
//
if (key in result) continue;
result[key] = value;
}
return result;
}
/**
* Transform a query string to an object.
*
* @param {Object} obj Object that should be transformed.
* @param {String} prefix Optional prefix.
* @returns {String}
* @api public
*/
export function stringify(obj, prefix) {
prefix = prefix || '';
var pairs = []
, value
, key;
//
// Optionally prefix with a '?' if needed
//
if ('string' !== typeof prefix) prefix = '?';
for (key in obj) {
if (has.call(obj, key)) {
value = obj[key];
//
// Edge cases where we actually want to encode the value to an empty
// string instead of the stringified value.
//
if (!value && (value === null || value === undef || isNaN(value))) {
value = '';
}
pairs.push(encodeURIComponent(key) +'='+ encodeURIComponent(value));
}
}
return pairs.length ? prefix + pairs.join('&') : '';
}

View File

@ -14,9 +14,10 @@ import (
type (
Settings struct {
ServerAddress string
CDPAddress string
Dir string
StaticServerAddress string
DynamicServerAddress string
CDPAddress string
Dir string
}
Result struct {
@ -129,7 +130,8 @@ func (r *Runner) runQuery(c *compiler.FqlCompiler, name, script string) Result {
out, err := p.Run(
context.Background(),
runtime.WithBrowser(r.settings.CDPAddress),
runtime.WithParam("server", r.settings.ServerAddress),
runtime.WithParam("static", r.settings.StaticServerAddress),
runtime.WithParam("dynamic", r.settings.DynamicServerAddress),
)
duration := time.Now().Sub(start)

View File

@ -4,6 +4,7 @@ import (
"context"
"fmt"
"github.com/labstack/echo"
"path/filepath"
)
type (
@ -23,6 +24,7 @@ func New(settings Settings) *Server {
e.HideBanner = true
e.Static("/", settings.Dir)
e.File("/", filepath.Join(settings.Dir, "index.html"))
return &Server{e, settings}
}

View File

@ -1,4 +1,4 @@
LET url = @server + '/bootstrap/overview.html'
LET url = @static + '/overview.html'
LET doc = DOCUMENT(url)
LET expected = '<li class="toc-entry toc-h2"><a href="#containers">Containers</a></li><li class="toc-entry toc-h2"><a href="#responsive-breakpoints">Responsive breakpoints</a></li><li class="toc-entry toc-h2"><a href="#z-index">Z-index</a></li>'

View File

@ -1,4 +1,4 @@
LET url = @server + '/bootstrap/overview.html'
LET url = @static + '/overview.html'
LET doc = DOCUMENT(url)
LET expected = [

View File

@ -0,0 +1,12 @@
LET url = @dynamic
LET doc = DOCUMENT(url, true)
WAIT_ELEMENT(doc, "#layout")
LET expected = [
'<h1>Welcome to Ferret E2E test page!</h1>',
'<p class="lead">It has several pages for testing different possibilities of the library</p>'
]
LET actual = INNER_HTML_ALL(doc, '#root > div > main > div > *')
RETURN EXPECT(expected, actual)

View File

@ -0,0 +1,10 @@
LET url = @dynamic
LET doc = DOCUMENT(url, true)
LET selector = '#root > div > main > div'
WAIT_ELEMENT(doc, "#layout")
LET expected = '<div><h1>Welcome to Ferret E2E test page!</h1></div><div><p class="lead">It has several pages for testing different possibilities of the library</p></div>'
LET actual = INNER_HTML(doc, selector)
RETURN EXPECT(REGEXP_REPLACE(expected, '\s', ''), REGEXP_REPLACE(TRIM(actual), '(\n|\s)', ''))

View File

@ -1,4 +1,4 @@
LET url = @server + '/bootstrap/overview.html'
LET url = @static + '/overview.html'
LET doc = DOCUMENT(url)
LET expected = "Components and options for laying out your Bootstrap project, including wrapping containers, a powerful grid system, a flexible media object, and responsive utility classes."

View File

@ -1,4 +1,4 @@
LET url = @server + '/bootstrap/grid.html'
LET url = @static + '/grid.html'
LET doc = DOCUMENT(url)
LET expected = [

View File

@ -0,0 +1,16 @@
LET url = @dynamic
LET doc = DOCUMENT(url, true)
LET selector = '#root > div > main > div > *'
WAIT_ELEMENT(doc, "#layout")
LET expected = [
'Welcome to Ferret E2E test page!',
'It has several pages for testing different possibilities of the library'
]
LET actual = (
FOR str IN INNER_TEXT_ALL(doc, selector)
RETURN REGEXP_REPLACE(TRIM(str), '\n', '')
)
RETURN EXPECT(expected, actual)

View File

@ -0,0 +1,10 @@
LET url = @dynamic
LET doc = DOCUMENT(url, true)
LET selector = '#root > div > main > div h1'
WAIT_ELEMENT(doc, "#layout")
LET expected = 'Welcome to Ferret E2E test page!'
LET actual = INNER_TEXT(doc, selector)
RETURN EXPECT(REGEXP_REPLACE(expected, '\s', ''), REGEXP_REPLACE(TRIM(actual), '(\n|\s)', ''))

View File

@ -0,0 +1,10 @@
LET url = @dynamic + "?redirect=/forms"
LET doc = DOCUMENT(url, true)
WAIT_ELEMENT(doc, "form")
LET output = ELEMENT(doc, "#text_output")
INPUT(doc, "#text_input", "foo")
RETURN EXPECT(output.innerText, "foo")

View File

@ -0,0 +1,9 @@
LET url = @dynamic + "?redirect=/forms"
LET doc = DOCUMENT(url, true)
WAIT_ELEMENT(doc, "form")
LET output = ELEMENT(doc, "#multi_select_output")
LET result = SELECT(doc, "#multi_select_input", ["1", "2", "4"])
RETURN EXPECT(output.innerText, "1, 2, 4") + EXPECT(JSON_STRINGIFY(result), '["1","2","4"]')

View File

@ -0,0 +1,9 @@
LET url = @dynamic + "?redirect=/forms"
LET doc = DOCUMENT(url, true)
WAIT_ELEMENT(doc, "form")
LET output = ELEMENT(doc, "#select_output")
LET result = SELECT(doc, "#select_input", ["4"])
RETURN EXPECT(output.innerText, "4") + EXPECT(JSON_STRINGIFY(result), '["4"]')

View File

@ -0,0 +1,11 @@
LET url = @dynamic + "?redirect=/forms"
LET doc = DOCUMENT(url, true)
WAIT_ELEMENT(doc, "form")
LET input = ELEMENT(doc, "#text_input")
LET output = ELEMENT(doc, "#text_output")
INPUT(input, "foo")
RETURN EXPECT(output.innerText, "foo")

View File

@ -0,0 +1,10 @@
LET url = @dynamic + "?redirect=/forms"
LET doc = DOCUMENT(url, true)
WAIT_ELEMENT(doc, "form")
LET input = ELEMENT(doc, "#multi_select_input")
LET output = ELEMENT(doc, "#multi_select_output")
LET result = SELECT(input, ["1", "2", "4"])
RETURN EXPECT(output.innerText, "1, 2, 4") + EXPECT(JSON_STRINGIFY(result), '["1","2","4"]')

View File

@ -0,0 +1,10 @@
LET url = @dynamic + "?redirect=/forms"
LET doc = DOCUMENT(url, true)
WAIT_ELEMENT(doc, "form")
LET input = ELEMENT(doc, "#select_input")
LET output = ELEMENT(doc, "#select_output")
LET result = SELECT(input, ["4"])
RETURN EXPECT(output.innerText, "4") + EXPECT(JSON_STRINGIFY(result), '["4"]')

View File

@ -1,10 +0,0 @@
// LET url = @server + '/bootstrap/overview.html'
// LET doc = DOCUMENT(url, true)
// LET selector = '.section-nav'
// LET expected = '<li class="toc-entry toc-h2"><a href="#containers">Containers</a></li><li class="toc-entry toc-h2"><a href="#responsive-breakpoints">Responsive breakpoints</a></li><li class="toc-entry toc-h2"><a href="#z-index">Z-index</a></li>'
// LET actual = INNER_HTML(doc, selector)
// RETURN EXPECT(REGEXP_REPLACE(expected, '\s', ''), REGEXP_REPLACE(TRIM(actual), '(\n|\s)', ''))
RETURN ""

View File

@ -1,9 +0,0 @@
// LET url = @server + '/bootstrap/overview.html'
// LET doc = DOCUMENT(url, true)
// LET selector = 'body > div > div > main > p.bd-lead'
// LET expected = "Components and options for laying out your Bootstrap project, including wrapping containers, a powerful grid system, a flexible media object, and responsive utility classes."
// LET actual = INNER_TEXT(doc, selector)
// RETURN EXPECT(expected, actual)
RETURN ""

View File

@ -1,4 +1,4 @@
LET url = @server + '/bootstrap/overview.html'
LET url = @static + '/overview.html'
LET doc = DOCUMENT(url)
RETURN EXPECT(doc.url, url)

View File

@ -0,0 +1,4 @@
LET url = @dynamic
LET doc = DOCUMENT(url, true)
RETURN EXPECT(doc.url, url)

View File

@ -356,7 +356,7 @@ func (doc *HTMLDocument) InnerTextBySelector(selector values.String) values.Stri
doc.Lock()
defer doc.Unlock()
return doc.element.InnerHTMLBySelector(selector)
return doc.element.InnerTextBySelector(selector)
}
func (doc *HTMLDocument) InnerTextBySelectorAll(selector values.String) *values.Array {
@ -479,6 +479,58 @@ func (doc *HTMLDocument) InputBySelector(selector values.String, value core.Valu
return values.True, nil
}
func (doc *HTMLDocument) SelectBySelector(selector values.String, value *values.Array) (*values.Array, error) {
res, err := eval.Eval(
doc.client,
fmt.Sprintf(`
var element = document.querySelector(%s);
if (element == null) {
return [];
}
var values = %s;
if (element.nodeName.toLowerCase() !== 'select') {
throw new Error('Element is not a <select> element.');
}
var options = Array.from(element.options);
element.value = undefined;
for (var option of options) {
option.selected = values.includes(option.value);
if (option.selected && !element.multiple) {
break;
}
}
element.dispatchEvent(new Event('input', { 'bubbles': true }));
element.dispatchEvent(new Event('change', { 'bubbles': true }));
return options.filter(option => option.selected).map(option => option.value);
`,
eval.ParamString(selector.String()),
value.String(),
),
true,
false,
)
if err != nil {
return nil, err
}
arr, ok := res.(*values.Array)
if ok {
return arr, nil
}
return nil, core.TypeError(core.ArrayType, res.Type())
}
func (doc *HTMLDocument) WaitForSelector(selector values.String, timeout values.Int) error {
task := events.NewEvalWaitTask(
doc.client,
@ -702,6 +754,7 @@ func (doc *HTMLDocument) PrintToPDF(params *page.PrintToPDFArgs) (core.Value, er
ctx := context.Background()
reply, err := doc.client.Page.PrintToPDF(ctx, params)
if err != nil {
return values.None, err
}

View File

@ -4,6 +4,7 @@ import (
"context"
"encoding/json"
"fmt"
"github.com/gofrs/uuid"
"hash/fnv"
"strconv"
"strings"
@ -27,6 +28,7 @@ const DefaultTimeout = time.Second * 30
var emptyNodeID = dom.NodeID(0)
var emptyBackendID = dom.BackendNodeID(0)
var emptyObjectID = ""
var attrID = "data-ferret-id"
type (
HTMLElementIdentity struct {
@ -746,6 +748,78 @@ func (el *HTMLElement) Input(value core.Value, delay values.Int) error {
return nil
}
func (el *HTMLElement) Select(value *values.Array) (*values.Array, error) {
if el.NodeName() != "SELECT" {
return nil, core.Error(core.ErrInvalidOperation, "Element is not a <select> element.")
}
id, err := uuid.NewV4()
if err != nil {
return nil, err
}
ctx, cancel := contextWithTimeout()
defer cancel()
err = el.client.DOM.SetAttributeValue(ctx, dom.NewSetAttributeValueArgs(el.id.nodeID, attrID, id.String()))
if err != nil {
return nil, err
}
res, err := eval.Eval(
el.client,
fmt.Sprintf(`
var element = document.querySelector('[%s="%s"]');
if (element == null) {
return [];
}
var values = %s;
if (element.nodeName.toLowerCase() !== 'select') {
throw new Error('Element is not a <select> element.');
}
var options = Array.from(element.options);
element.value = undefined;
for (var option of options) {
option.selected = values.includes(option.value);
if (option.selected && !element.multiple) {
break;
}
}
element.dispatchEvent(new Event('input', { 'bubbles': true }));
element.dispatchEvent(new Event('change', { 'bubbles': true }));
return options.filter(option => option.selected).map(option => option.value);
`,
attrID,
id.String(),
value.String(),
),
true,
false,
)
if err != nil {
return nil, err
}
arr, ok := res.(*values.Array)
if ok {
return arr, nil
}
return nil, core.TypeError(core.ArrayType, res.Type())
}
func (el *HTMLElement) IsConnected() values.Boolean {
el.mu.Lock()
defer el.mu.Unlock()

View File

@ -3,13 +3,16 @@ package dynamic
import (
"bytes"
"context"
"errors"
"github.com/MontFerret/ferret/pkg/html/common"
"github.com/MontFerret/ferret/pkg/html/dynamic/eval"
"github.com/MontFerret/ferret/pkg/html/dynamic/events"
"github.com/MontFerret/ferret/pkg/runtime/values"
"github.com/PuerkitoBio/goquery"
"github.com/mafredri/cdp"
"github.com/mafredri/cdp/protocol/dom"
"github.com/mafredri/cdp/protocol/page"
"github.com/mafredri/cdp/protocol/runtime"
"golang.org/x/sync/errgroup"
"strings"
)
@ -65,23 +68,43 @@ func parseAttrs(attrs []string) *values.Object {
}
func loadInnerHTML(ctx context.Context, client *cdp.Client, id *HTMLElementIdentity) (values.String, error) {
var args *dom.GetOuterHTMLArgs
var objID runtime.RemoteObjectID
if id.objectID != "" {
args = dom.NewGetOuterHTMLArgs().SetObjectID(id.objectID)
objID = id.objectID
} else if id.backendID > 0 {
args = dom.NewGetOuterHTMLArgs().SetBackendNodeID(id.backendID)
repl, err := client.DOM.ResolveNode(ctx, dom.NewResolveNodeArgs().SetBackendNodeID(id.backendID))
if err != nil {
return "", err
}
if repl.Object.ObjectID == nil {
return "", errors.New("unable to resolve node")
}
objID = *repl.Object.ObjectID
} else {
args = dom.NewGetOuterHTMLArgs().SetNodeID(id.nodeID)
repl, err := client.DOM.ResolveNode(ctx, dom.NewResolveNodeArgs().SetNodeID(id.nodeID))
if err != nil {
return "", err
}
if repl.Object.ObjectID == nil {
return "", errors.New("unable to resolve node")
}
objID = *repl.Object.ObjectID
}
res, err := client.DOM.GetOuterHTML(ctx, args)
res, err := eval.Property(ctx, client, objID, "innerHTML")
if err != nil {
return "", err
}
return values.NewString(res.OuterHTML), err
return values.NewString(res.String()), err
}
func parseInnerText(innerHTML string) (values.String, error) {

View File

@ -30,7 +30,6 @@ func Input(_ context.Context, args ...core.Value) (core.Value, error) {
switch args[0].(type) {
case *dynamic.HTMLDocument:
doc, ok := arg1.(*dynamic.HTMLDocument)
if !ok {
@ -46,6 +45,7 @@ func Input(_ context.Context, args ...core.Value) (core.Value, error) {
}
delay := values.Int(0)
if len(args) == 4 {
arg4 := args[3]
@ -58,7 +58,6 @@ func Input(_ context.Context, args ...core.Value) (core.Value, error) {
}
return doc.InputBySelector(arg2.(values.String), args[2], delay)
case *dynamic.HTMLElement:
el, ok := arg1.(*dynamic.HTMLElement)
@ -67,6 +66,7 @@ func Input(_ context.Context, args ...core.Value) (core.Value, error) {
}
delay := values.Int(0)
if len(args) == 3 {
arg3 := args[2]

View File

@ -32,6 +32,7 @@ func NewLib() map[string]core.Function {
"INNER_HTML_ALL": InnerHTMLAll,
"INNER_TEXT": InnerText,
"INNER_TEXT_ALL": InnerTextAll,
"SELECT": Select,
"SCREENSHOT": Screenshot,
"PDF": PDF,
"DOWNLOAD": Download,

72
pkg/stdlib/html/select.go Normal file
View File

@ -0,0 +1,72 @@
package html
import (
"context"
"github.com/MontFerret/ferret/pkg/html/dynamic"
"github.com/MontFerret/ferret/pkg/runtime/core"
"github.com/MontFerret/ferret/pkg/runtime/values"
)
// Select selects a value from an underlying select element.
// @param source (Document | Element) - Event target.
// @param valueOrSelector (String | Array<String>) - Selector or a an array of strings as a value.
// @param value (Array<String) - Target value. Optional.
// @returns (Array<String>) - Returns an array of selected values.
func Select(_ context.Context, args ...core.Value) (core.Value, error) {
err := core.ValidateArgs(args, 2, 4)
if err != nil {
return values.None, err
}
arg1 := args[0]
err = core.ValidateType(arg1, core.HTMLDocumentType, core.HTMLElementType)
if err != nil {
return values.False, err
}
switch args[0].(type) {
case *dynamic.HTMLDocument:
doc, ok := arg1.(*dynamic.HTMLDocument)
if !ok {
return values.False, core.Errors(core.ErrInvalidType, ErrNotDynamic)
}
// selector
arg2 := args[1]
err = core.ValidateType(arg2, core.StringType)
if err != nil {
return values.False, err
}
arg3 := args[2]
err = core.ValidateType(arg3, core.ArrayType)
if err != nil {
return values.False, err
}
return doc.SelectBySelector(arg2.(values.String), arg3.(*values.Array))
case *dynamic.HTMLElement:
el, ok := arg1.(*dynamic.HTMLElement)
if !ok {
return values.False, core.Errors(core.ErrInvalidType, ErrNotDynamic)
}
arg2 := args[1]
err = core.ValidateType(arg2, core.ArrayType)
if err != nil {
return values.False, err
}
return el.Select(arg2.(*values.Array))
default:
return values.False, core.Errors(core.ErrInvalidArgument)
}
}