initial commit
This commit is contained in:
commit
a9e8312934
183
driver.go
Normal file
183
driver.go
Normal file
@ -0,0 +1,183 @@
|
||||
package webdriver
|
||||
|
||||
import (
|
||||
"bufio"
|
||||
"bytes"
|
||||
"context"
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"io"
|
||||
"net/http"
|
||||
"net/url"
|
||||
"os/exec"
|
||||
"strconv"
|
||||
)
|
||||
|
||||
type Driver struct {
|
||||
server *exec.Cmd
|
||||
|
||||
host string
|
||||
port uint16
|
||||
sessionID string
|
||||
}
|
||||
|
||||
func NewDriver() (*Driver, error) {
|
||||
host := "127.0.0.1"
|
||||
port := uint16(4444)
|
||||
|
||||
ctx := context.Background()
|
||||
server := exec.CommandContext(ctx, "geckodriver", "--host", host, "--port", strconv.Itoa(int(port)))
|
||||
|
||||
stdout, err := server.StdoutPipe()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
if err := server.Start(); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
scanner := bufio.NewScanner(stdout)
|
||||
scanner.Scan()
|
||||
|
||||
return &Driver{
|
||||
server: server,
|
||||
host: host,
|
||||
port: port,
|
||||
}, nil
|
||||
}
|
||||
|
||||
func UseDriver(host string, port uint16) *Driver {
|
||||
return &Driver{
|
||||
host: host,
|
||||
port: port,
|
||||
}
|
||||
}
|
||||
|
||||
func (d *Driver) Close() error {
|
||||
if d.server == nil {
|
||||
return nil
|
||||
}
|
||||
|
||||
if d.sessionID != "" {
|
||||
if err := d.CloseSession(); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
if err := d.server.Cancel(); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if err := d.server.Wait(); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (d *Driver) buildSessionUrl(path string) string {
|
||||
uri, err := url.JoinPath(fmt.Sprintf("http://%s:%d/session/%s", d.host, d.port, d.sessionID), path)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
return uri
|
||||
}
|
||||
|
||||
func (d *Driver) buildUrl(path string) string {
|
||||
uri, err := url.JoinPath(fmt.Sprintf("http://%s:%d", d.host, d.port), path)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
return uri
|
||||
}
|
||||
|
||||
func (d *Driver) DoRawSessionRequest(method string, path string, body io.Reader) (*http.Response, error) {
|
||||
req, err := http.NewRequest(method, d.buildSessionUrl(path), body)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
req.Header.Add("Content-Type", "application/json")
|
||||
|
||||
return http.DefaultClient.Do(req)
|
||||
}
|
||||
|
||||
func (d *Driver) DoRawRequest(method string, path string, body io.Reader) (*http.Response, error) {
|
||||
req, err := http.NewRequest(method, d.buildUrl(path), body)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
req.Header.Add("Content-Type", "application/json")
|
||||
|
||||
return http.DefaultClient.Do(req)
|
||||
}
|
||||
|
||||
func (d *Driver) DoSessionRequest(method string, path string, body map[string]interface{}) (map[string]interface{}, error) {
|
||||
if body == nil {
|
||||
body = map[string]interface{}{}
|
||||
}
|
||||
|
||||
data, err := json.Marshal(body)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
resp, err := d.DoRawSessionRequest(method, path, bytes.NewReader(data))
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
defer resp.Body.Close()
|
||||
|
||||
respBody := map[string]interface{}{}
|
||||
if err := json.NewDecoder(resp.Body).Decode(&respBody); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return respBody, nil
|
||||
}
|
||||
|
||||
func (d *Driver) DoRequest(method string, path string, body map[string]interface{}) (map[string]interface{}, error) {
|
||||
if body == nil {
|
||||
body = map[string]interface{}{}
|
||||
}
|
||||
|
||||
data, err := json.Marshal(body)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
resp, err := d.DoRawRequest(method, path, bytes.NewReader(data))
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
defer resp.Body.Close()
|
||||
|
||||
respBody := map[string]interface{}{}
|
||||
if err := json.NewDecoder(resp.Body).Decode(&respBody); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return respBody, nil
|
||||
}
|
||||
|
||||
func (d *Driver) NewSession() error {
|
||||
data, err := d.DoRequest(http.MethodPost, "/session", nil)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
d.sessionID = data["value"].(map[string]interface{})["sessionId"].(string)
|
||||
return nil
|
||||
}
|
||||
|
||||
func (d *Driver) CloseSession() error {
|
||||
_, err := d.DoSessionRequest(http.MethodDelete, "", nil)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
d.sessionID = ""
|
||||
return nil
|
||||
}
|
140
driver_methods.go
Normal file
140
driver_methods.go
Normal file
@ -0,0 +1,140 @@
|
||||
package webdriver
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"net/http"
|
||||
|
||||
"git.milar.in/milarin/slices"
|
||||
)
|
||||
|
||||
// docs: https://w3c.github.io/webdriver/
|
||||
|
||||
func (d *Driver) Navigate(url string) error {
|
||||
_, err := d.DoSessionRequest(http.MethodPost, "/url", map[string]interface{}{"url": url})
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (d *Driver) GetUrl() (string, error) {
|
||||
data, err := d.DoSessionRequest(http.MethodGet, "/url", nil)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
return data["value"].(string), nil
|
||||
}
|
||||
|
||||
func (d *Driver) GetTitle() (string, error) {
|
||||
data, err := d.DoSessionRequest(http.MethodGet, "/title", nil)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
return data["value"].(string), nil
|
||||
}
|
||||
|
||||
func (d *Driver) FindElement(selector string) (string, error) {
|
||||
req := map[string]interface{}{
|
||||
"using": "css selector",
|
||||
"value": selector,
|
||||
}
|
||||
|
||||
data, err := d.DoSessionRequest(http.MethodPost, "/element", req)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
return data["value"].(map[string]interface{})[webElementIdentifier].(string), nil
|
||||
}
|
||||
|
||||
func (d *Driver) FindElements(selector string) ([]string, error) {
|
||||
req := map[string]interface{}{
|
||||
"using": "css selector",
|
||||
"value": selector,
|
||||
}
|
||||
|
||||
data, err := d.DoSessionRequest(http.MethodPost, "/elements", req)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return slices.Map(data["value"].([]interface{}), func(e interface{}) string {
|
||||
return e.(map[string]interface{})[webElementIdentifier].(string)
|
||||
}), nil
|
||||
}
|
||||
|
||||
func (d *Driver) GetElementTagName(elementID string) (string, error) {
|
||||
data, err := d.DoSessionRequest(http.MethodGet, fmt.Sprintf("/element/%s/name", elementID), nil)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
return data["value"].(string), nil
|
||||
}
|
||||
|
||||
func (d *Driver) GetElementText(elementID string) (string, error) {
|
||||
data, err := d.DoSessionRequest(http.MethodGet, fmt.Sprintf("/element/%s/text", elementID), nil)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
return data["value"].(string), nil
|
||||
}
|
||||
|
||||
func (d *Driver) GetElementRect(elementID string) (*Rect, error) {
|
||||
data, err := d.DoSessionRequest(http.MethodGet, fmt.Sprintf("/element/%s/rect", elementID), nil)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
rect := data["value"].(map[string]interface{})
|
||||
return &Rect{
|
||||
X: rect["x"].(float64),
|
||||
Y: rect["y"].(float64),
|
||||
Width: rect["width"].(float64),
|
||||
Height: rect["height"].(float64),
|
||||
}, nil
|
||||
}
|
||||
|
||||
func (d *Driver) GetElementCssValue(elementID, property string) (string, error) {
|
||||
data, err := d.DoSessionRequest(http.MethodGet, fmt.Sprintf("/element/%s/css/%s", elementID, property), nil)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
return data["value"].(string), nil
|
||||
}
|
||||
|
||||
func (d *Driver) GetElementProperty(elementID, property string) (string, error) {
|
||||
data, err := d.DoSessionRequest(http.MethodGet, fmt.Sprintf("/element/%s/property/%s", elementID, property), nil)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
return data["value"].(string), nil
|
||||
}
|
||||
|
||||
func (d *Driver) GetElementAttribute(elementID, attribute string) (string, error) {
|
||||
data, err := d.DoSessionRequest(http.MethodGet, fmt.Sprintf("/element/%s/attribute/%s", elementID, attribute), nil)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
return data["value"].(string), nil
|
||||
}
|
||||
|
||||
func (d *Driver) ClickElement(elementID string) error {
|
||||
data, err := d.DoSessionRequest(http.MethodPost, fmt.Sprintf("/element/%s/click", elementID), nil)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
fmt.Printf("%#v\n", data)
|
||||
return nil
|
||||
}
|
||||
|
||||
func (d *Driver) SendKeysToElement(elementID, text string) error {
|
||||
req := map[string]interface{}{
|
||||
"text": text,
|
||||
}
|
||||
|
||||
data, err := d.DoSessionRequest(http.MethodPost, fmt.Sprintf("/element/%s/value", elementID), req)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
fmt.Printf("%#v\n", data)
|
||||
return nil
|
||||
}
|
18
driver_test.go
Normal file
18
driver_test.go
Normal file
@ -0,0 +1,18 @@
|
||||
package webdriver
|
||||
|
||||
import (
|
||||
"testing"
|
||||
)
|
||||
|
||||
func TestDriver(t *testing.T) {
|
||||
driver, err := NewDriver()
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
defer driver.Close()
|
||||
|
||||
if err := driver.NewSession(); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
defer driver.CloseSession()
|
||||
}
|
7
go.mod
Normal file
7
go.mod
Normal file
@ -0,0 +1,7 @@
|
||||
module git.milar.in/milarin/webdriver
|
||||
|
||||
go 1.21.5
|
||||
|
||||
require git.milar.in/milarin/slices v0.0.8
|
||||
|
||||
require git.milar.in/milarin/gmath v0.0.3 // indirect
|
4
go.sum
Normal file
4
go.sum
Normal file
@ -0,0 +1,4 @@
|
||||
git.milar.in/milarin/gmath v0.0.3 h1:ii6rKNItS55O/wtIFhD1cTN2BMwDZjTBmiOocKURvxM=
|
||||
git.milar.in/milarin/gmath v0.0.3/go.mod h1:HDLftG5RLpiNGKiIWh+O2G1PYkNzyLDADO8Cd/1abiE=
|
||||
git.milar.in/milarin/slices v0.0.8 h1:qN9TE3tkArdTixMKSnwvNPcApwAjxpLVwA5a9k1rm2s=
|
||||
git.milar.in/milarin/slices v0.0.8/go.mod h1:qMhdtMnfWswc1rHpwgNw33lB84aNEkdBn5BDiYA+G3k=
|
8
types.go
Normal file
8
types.go
Normal file
@ -0,0 +1,8 @@
|
||||
package webdriver
|
||||
|
||||
type Rect struct {
|
||||
X float64
|
||||
Y float64
|
||||
Width float64
|
||||
Height float64
|
||||
}
|
Loading…
Reference in New Issue
Block a user