mirror of
https://github.com/MontFerret/ferret.git
synced 2025-02-03 13:11:45 +02:00
Integration tests (#170)
This commit is contained in:
parent
423d84cd07
commit
de774ba03e
@ -1,5 +1,14 @@
|
|||||||
## Changelog
|
## 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
|
### 0.4.0
|
||||||
#### Added
|
#### Added
|
||||||
- ``COLLECT`` keyword [#141](https://github.com/MontFerret/ferret/pull/141)
|
- ``COLLECT`` keyword [#141](https://github.com/MontFerret/ferret/pull/141)
|
||||||
|
49
e2e/main.go
49
e2e/main.go
@ -1,14 +1,14 @@
|
|||||||
package main
|
package main
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"context"
|
|
||||||
"flag"
|
"flag"
|
||||||
"fmt"
|
"fmt"
|
||||||
|
"os"
|
||||||
|
"path/filepath"
|
||||||
|
|
||||||
"github.com/MontFerret/ferret/e2e/runner"
|
"github.com/MontFerret/ferret/e2e/runner"
|
||||||
"github.com/MontFerret/ferret/e2e/server"
|
"github.com/MontFerret/ferret/e2e/server"
|
||||||
"github.com/rs/zerolog"
|
"github.com/rs/zerolog"
|
||||||
"os"
|
|
||||||
"path/filepath"
|
|
||||||
)
|
)
|
||||||
|
|
||||||
var (
|
var (
|
||||||
@ -24,12 +24,6 @@ var (
|
|||||||
"root directory with test pages",
|
"root directory with test pages",
|
||||||
)
|
)
|
||||||
|
|
||||||
port = flag.Uint64(
|
|
||||||
"port",
|
|
||||||
8080,
|
|
||||||
"server port",
|
|
||||||
)
|
|
||||||
|
|
||||||
cdp = flag.String(
|
cdp = flag.String(
|
||||||
"cdp",
|
"cdp",
|
||||||
"http://0.0.0.0:9222",
|
"http://0.0.0.0:9222",
|
||||||
@ -42,17 +36,27 @@ func main() {
|
|||||||
|
|
||||||
logger := zerolog.New(zerolog.ConsoleWriter{Out: os.Stderr})
|
logger := zerolog.New(zerolog.ConsoleWriter{Out: os.Stderr})
|
||||||
|
|
||||||
s := server.New(server.Settings{
|
staticPort := uint64(8080)
|
||||||
Port: *port,
|
static := server.New(server.Settings{
|
||||||
Dir: *pagesDir,
|
Port: staticPort,
|
||||||
|
Dir: filepath.Join(*pagesDir, "static"),
|
||||||
})
|
})
|
||||||
|
|
||||||
ctx, cancel := context.WithCancel(context.Background())
|
dynamicPort := uint64(8081)
|
||||||
defer cancel()
|
dynamic := server.New(server.Settings{
|
||||||
|
Port: dynamicPort,
|
||||||
|
Dir: filepath.Join(*pagesDir, "dynamic"),
|
||||||
|
})
|
||||||
|
|
||||||
go func() {
|
go func() {
|
||||||
if err := s.Start(); err != nil {
|
if err := static.Start(); err != nil {
|
||||||
logger.Info().Timestamp().Msg("shutting down the server")
|
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{
|
r := runner.New(logger, runner.Settings{
|
||||||
ServerAddress: fmt.Sprintf("http://0.0.0.0:%d", *port),
|
StaticServerAddress: fmt.Sprintf("http://0.0.0.0:%d", staticPort),
|
||||||
CDPAddress: *cdp,
|
DynamicServerAddress: fmt.Sprintf("http://0.0.0.0:%d", dynamicPort),
|
||||||
Dir: *testsDir,
|
CDPAddress: *cdp,
|
||||||
|
Dir: *testsDir,
|
||||||
})
|
})
|
||||||
|
|
||||||
err := r.Run()
|
err := r.Run()
|
||||||
|
|
||||||
if err := s.Stop(ctx); err != nil {
|
|
||||||
logger.Fatal().Timestamp().Err(err).Msg("failed to stop server")
|
|
||||||
}
|
|
||||||
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
os.Exit(1)
|
os.Exit(1)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
os.Exit(0)
|
||||||
}
|
}
|
||||||
|
29
e2e/pages/dynamic/components/app.js
Normal file
29
e2e/pages/dynamic/components/app.js
Normal 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
|
||||||
|
])
|
||||||
|
)
|
||||||
|
}
|
27
e2e/pages/dynamic/components/layout.js
Normal file
27
e2e/pages/dynamic/components/layout.js
Normal 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)
|
||||||
|
])
|
||||||
|
}
|
124
e2e/pages/dynamic/components/pages/forms/index.js
Normal file
124
e2e/pages/dynamic/components/pages/forms/index.js
Normal 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
|
||||||
|
)
|
||||||
|
]),
|
||||||
|
])
|
||||||
|
}
|
||||||
|
}
|
8
e2e/pages/dynamic/components/pages/index.js
Normal file
8
e2e/pages/dynamic/components/pages/index.js
Normal 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"))
|
||||||
|
])
|
||||||
|
}
|
4
e2e/pages/dynamic/index.css
Normal file
4
e2e/pages/dynamic/index.css
Normal file
@ -0,0 +1,4 @@
|
|||||||
|
/* Show it's not fixed to the top */
|
||||||
|
body {
|
||||||
|
min-height: 75rem;
|
||||||
|
}
|
21
e2e/pages/dynamic/index.html
Normal file
21
e2e/pages/dynamic/index.html
Normal 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>
|
9
e2e/pages/dynamic/index.js
Normal file
9
e2e/pages/dynamic/index.js
Normal 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")
|
||||||
|
);
|
82
e2e/pages/dynamic/utils/qs.js
Normal file
82
e2e/pages/dynamic/utils/qs.js
Normal 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('&') : '';
|
||||||
|
}
|
@ -14,9 +14,10 @@ import (
|
|||||||
|
|
||||||
type (
|
type (
|
||||||
Settings struct {
|
Settings struct {
|
||||||
ServerAddress string
|
StaticServerAddress string
|
||||||
CDPAddress string
|
DynamicServerAddress string
|
||||||
Dir string
|
CDPAddress string
|
||||||
|
Dir string
|
||||||
}
|
}
|
||||||
|
|
||||||
Result struct {
|
Result struct {
|
||||||
@ -129,7 +130,8 @@ func (r *Runner) runQuery(c *compiler.FqlCompiler, name, script string) Result {
|
|||||||
out, err := p.Run(
|
out, err := p.Run(
|
||||||
context.Background(),
|
context.Background(),
|
||||||
runtime.WithBrowser(r.settings.CDPAddress),
|
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)
|
duration := time.Now().Sub(start)
|
||||||
|
@ -4,6 +4,7 @@ import (
|
|||||||
"context"
|
"context"
|
||||||
"fmt"
|
"fmt"
|
||||||
"github.com/labstack/echo"
|
"github.com/labstack/echo"
|
||||||
|
"path/filepath"
|
||||||
)
|
)
|
||||||
|
|
||||||
type (
|
type (
|
||||||
@ -23,6 +24,7 @@ func New(settings Settings) *Server {
|
|||||||
e.HideBanner = true
|
e.HideBanner = true
|
||||||
|
|
||||||
e.Static("/", settings.Dir)
|
e.Static("/", settings.Dir)
|
||||||
|
e.File("/", filepath.Join(settings.Dir, "index.html"))
|
||||||
|
|
||||||
return &Server{e, settings}
|
return &Server{e, settings}
|
||||||
}
|
}
|
||||||
|
@ -1,4 +1,4 @@
|
|||||||
LET url = @server + '/bootstrap/overview.html'
|
LET url = @static + '/overview.html'
|
||||||
LET doc = DOCUMENT(url)
|
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>'
|
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>'
|
@ -1,4 +1,4 @@
|
|||||||
LET url = @server + '/bootstrap/overview.html'
|
LET url = @static + '/overview.html'
|
||||||
LET doc = DOCUMENT(url)
|
LET doc = DOCUMENT(url)
|
||||||
|
|
||||||
LET expected = [
|
LET expected = [
|
12
e2e/tests/doc_inner_html_all_d.fql
Normal file
12
e2e/tests/doc_inner_html_all_d.fql
Normal 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)
|
10
e2e/tests/doc_inner_html_d.fql
Normal file
10
e2e/tests/doc_inner_html_d.fql
Normal 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)', ''))
|
@ -1,4 +1,4 @@
|
|||||||
LET url = @server + '/bootstrap/overview.html'
|
LET url = @static + '/overview.html'
|
||||||
LET doc = DOCUMENT(url)
|
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."
|
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."
|
@ -1,4 +1,4 @@
|
|||||||
LET url = @server + '/bootstrap/grid.html'
|
LET url = @static + '/grid.html'
|
||||||
LET doc = DOCUMENT(url)
|
LET doc = DOCUMENT(url)
|
||||||
|
|
||||||
LET expected = [
|
LET expected = [
|
16
e2e/tests/doc_inner_text_all_d.fql
Normal file
16
e2e/tests/doc_inner_text_all_d.fql
Normal 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)
|
10
e2e/tests/doc_inner_text_d.fql
Normal file
10
e2e/tests/doc_inner_text_d.fql
Normal 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)', ''))
|
10
e2e/tests/doc_input_text_d.fql
Normal file
10
e2e/tests/doc_input_text_d.fql
Normal 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")
|
9
e2e/tests/doc_select_multi_d.fql
Normal file
9
e2e/tests/doc_select_multi_d.fql
Normal 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"]')
|
9
e2e/tests/doc_select_single_d.fql
Normal file
9
e2e/tests/doc_select_single_d.fql
Normal 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"]')
|
11
e2e/tests/el_input_text_d.fql
Normal file
11
e2e/tests/el_input_text_d.fql
Normal 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")
|
10
e2e/tests/el_select_multi_d.fql
Normal file
10
e2e/tests/el_select_multi_d.fql
Normal 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"]')
|
10
e2e/tests/el_select_single_d.fql
Normal file
10
e2e/tests/el_select_single_d.fql
Normal 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"]')
|
@ -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 ""
|
|
@ -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 ""
|
|
@ -1,4 +1,4 @@
|
|||||||
LET url = @server + '/bootstrap/overview.html'
|
LET url = @static + '/overview.html'
|
||||||
LET doc = DOCUMENT(url)
|
LET doc = DOCUMENT(url)
|
||||||
|
|
||||||
RETURN EXPECT(doc.url, url)
|
RETURN EXPECT(doc.url, url)
|
4
e2e/tests/page_load_d.fql
Normal file
4
e2e/tests/page_load_d.fql
Normal file
@ -0,0 +1,4 @@
|
|||||||
|
LET url = @dynamic
|
||||||
|
LET doc = DOCUMENT(url, true)
|
||||||
|
|
||||||
|
RETURN EXPECT(doc.url, url)
|
@ -356,7 +356,7 @@ func (doc *HTMLDocument) InnerTextBySelector(selector values.String) values.Stri
|
|||||||
doc.Lock()
|
doc.Lock()
|
||||||
defer doc.Unlock()
|
defer doc.Unlock()
|
||||||
|
|
||||||
return doc.element.InnerHTMLBySelector(selector)
|
return doc.element.InnerTextBySelector(selector)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (doc *HTMLDocument) InnerTextBySelectorAll(selector values.String) *values.Array {
|
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
|
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 {
|
func (doc *HTMLDocument) WaitForSelector(selector values.String, timeout values.Int) error {
|
||||||
task := events.NewEvalWaitTask(
|
task := events.NewEvalWaitTask(
|
||||||
doc.client,
|
doc.client,
|
||||||
@ -702,6 +754,7 @@ func (doc *HTMLDocument) PrintToPDF(params *page.PrintToPDFArgs) (core.Value, er
|
|||||||
ctx := context.Background()
|
ctx := context.Background()
|
||||||
|
|
||||||
reply, err := doc.client.Page.PrintToPDF(ctx, params)
|
reply, err := doc.client.Page.PrintToPDF(ctx, params)
|
||||||
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return values.None, err
|
return values.None, err
|
||||||
}
|
}
|
||||||
|
@ -4,6 +4,7 @@ import (
|
|||||||
"context"
|
"context"
|
||||||
"encoding/json"
|
"encoding/json"
|
||||||
"fmt"
|
"fmt"
|
||||||
|
"github.com/gofrs/uuid"
|
||||||
"hash/fnv"
|
"hash/fnv"
|
||||||
"strconv"
|
"strconv"
|
||||||
"strings"
|
"strings"
|
||||||
@ -27,6 +28,7 @@ const DefaultTimeout = time.Second * 30
|
|||||||
var emptyNodeID = dom.NodeID(0)
|
var emptyNodeID = dom.NodeID(0)
|
||||||
var emptyBackendID = dom.BackendNodeID(0)
|
var emptyBackendID = dom.BackendNodeID(0)
|
||||||
var emptyObjectID = ""
|
var emptyObjectID = ""
|
||||||
|
var attrID = "data-ferret-id"
|
||||||
|
|
||||||
type (
|
type (
|
||||||
HTMLElementIdentity struct {
|
HTMLElementIdentity struct {
|
||||||
@ -746,6 +748,78 @@ func (el *HTMLElement) Input(value core.Value, delay values.Int) error {
|
|||||||
return nil
|
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 {
|
func (el *HTMLElement) IsConnected() values.Boolean {
|
||||||
el.mu.Lock()
|
el.mu.Lock()
|
||||||
defer el.mu.Unlock()
|
defer el.mu.Unlock()
|
||||||
|
@ -3,13 +3,16 @@ package dynamic
|
|||||||
import (
|
import (
|
||||||
"bytes"
|
"bytes"
|
||||||
"context"
|
"context"
|
||||||
|
"errors"
|
||||||
"github.com/MontFerret/ferret/pkg/html/common"
|
"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/html/dynamic/events"
|
||||||
"github.com/MontFerret/ferret/pkg/runtime/values"
|
"github.com/MontFerret/ferret/pkg/runtime/values"
|
||||||
"github.com/PuerkitoBio/goquery"
|
"github.com/PuerkitoBio/goquery"
|
||||||
"github.com/mafredri/cdp"
|
"github.com/mafredri/cdp"
|
||||||
"github.com/mafredri/cdp/protocol/dom"
|
"github.com/mafredri/cdp/protocol/dom"
|
||||||
"github.com/mafredri/cdp/protocol/page"
|
"github.com/mafredri/cdp/protocol/page"
|
||||||
|
"github.com/mafredri/cdp/protocol/runtime"
|
||||||
"golang.org/x/sync/errgroup"
|
"golang.org/x/sync/errgroup"
|
||||||
"strings"
|
"strings"
|
||||||
)
|
)
|
||||||
@ -65,23 +68,43 @@ func parseAttrs(attrs []string) *values.Object {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func loadInnerHTML(ctx context.Context, client *cdp.Client, id *HTMLElementIdentity) (values.String, error) {
|
func loadInnerHTML(ctx context.Context, client *cdp.Client, id *HTMLElementIdentity) (values.String, error) {
|
||||||
var args *dom.GetOuterHTMLArgs
|
var objID runtime.RemoteObjectID
|
||||||
|
|
||||||
if id.objectID != "" {
|
if id.objectID != "" {
|
||||||
args = dom.NewGetOuterHTMLArgs().SetObjectID(id.objectID)
|
objID = id.objectID
|
||||||
} else if id.backendID > 0 {
|
} 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 {
|
} 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 {
|
if err != nil {
|
||||||
return "", err
|
return "", err
|
||||||
}
|
}
|
||||||
|
|
||||||
return values.NewString(res.OuterHTML), err
|
return values.NewString(res.String()), err
|
||||||
}
|
}
|
||||||
|
|
||||||
func parseInnerText(innerHTML string) (values.String, error) {
|
func parseInnerText(innerHTML string) (values.String, error) {
|
||||||
|
@ -30,7 +30,6 @@ func Input(_ context.Context, args ...core.Value) (core.Value, error) {
|
|||||||
|
|
||||||
switch args[0].(type) {
|
switch args[0].(type) {
|
||||||
case *dynamic.HTMLDocument:
|
case *dynamic.HTMLDocument:
|
||||||
|
|
||||||
doc, ok := arg1.(*dynamic.HTMLDocument)
|
doc, ok := arg1.(*dynamic.HTMLDocument)
|
||||||
|
|
||||||
if !ok {
|
if !ok {
|
||||||
@ -46,6 +45,7 @@ func Input(_ context.Context, args ...core.Value) (core.Value, error) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
delay := values.Int(0)
|
delay := values.Int(0)
|
||||||
|
|
||||||
if len(args) == 4 {
|
if len(args) == 4 {
|
||||||
arg4 := args[3]
|
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)
|
return doc.InputBySelector(arg2.(values.String), args[2], delay)
|
||||||
|
|
||||||
case *dynamic.HTMLElement:
|
case *dynamic.HTMLElement:
|
||||||
el, ok := arg1.(*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)
|
delay := values.Int(0)
|
||||||
|
|
||||||
if len(args) == 3 {
|
if len(args) == 3 {
|
||||||
arg3 := args[2]
|
arg3 := args[2]
|
||||||
|
|
||||||
|
@ -32,6 +32,7 @@ func NewLib() map[string]core.Function {
|
|||||||
"INNER_HTML_ALL": InnerHTMLAll,
|
"INNER_HTML_ALL": InnerHTMLAll,
|
||||||
"INNER_TEXT": InnerText,
|
"INNER_TEXT": InnerText,
|
||||||
"INNER_TEXT_ALL": InnerTextAll,
|
"INNER_TEXT_ALL": InnerTextAll,
|
||||||
|
"SELECT": Select,
|
||||||
"SCREENSHOT": Screenshot,
|
"SCREENSHOT": Screenshot,
|
||||||
"PDF": PDF,
|
"PDF": PDF,
|
||||||
"DOWNLOAD": Download,
|
"DOWNLOAD": Download,
|
||||||
|
72
pkg/stdlib/html/select.go
Normal file
72
pkg/stdlib/html/select.go
Normal 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)
|
||||||
|
}
|
||||||
|
}
|
Loading…
x
Reference in New Issue
Block a user