1436 lines
36 KiB
Go
1436 lines
36 KiB
Go
package http
|
||
|
||
import (
|
||
"errors"
|
||
"fmt"
|
||
"io"
|
||
"strings"
|
||
"time"
|
||
|
||
"apigo.cc/gojs"
|
||
"github.com/go-rod/rod"
|
||
"github.com/go-rod/rod/lib/input"
|
||
"github.com/go-rod/rod/lib/proto"
|
||
"github.com/ssgo/u"
|
||
)
|
||
|
||
type Page struct {
|
||
page *rod.Page
|
||
longTimeoutPage *rod.Page
|
||
originPage *rod.Page
|
||
// Res []Resource
|
||
}
|
||
|
||
type Element struct {
|
||
element *rod.Element
|
||
page *rod.Page
|
||
}
|
||
|
||
type Resource struct {
|
||
Url string
|
||
Type string
|
||
Size int
|
||
MimeType string
|
||
LastModified string
|
||
Success bool
|
||
}
|
||
|
||
type Position struct {
|
||
X float64
|
||
Y float64
|
||
}
|
||
|
||
type Rect struct{ X, Y, Width, Height float64 }
|
||
|
||
func (ch *Chrome) Open(url string) (*Page, error) {
|
||
page, err := ch.browser.Page(proto.TargetCreateTarget{URL: url})
|
||
if err != nil {
|
||
return nil, gojs.Err(err)
|
||
}
|
||
|
||
pg := &Page{originPage: page}
|
||
pg.ResetTimeout()
|
||
pg.WaitPageLoad()
|
||
return pg, nil
|
||
}
|
||
|
||
func (pg *Page) Close() error {
|
||
// pg.Res = []Resource{}
|
||
return gojs.Err(pg.page.Close())
|
||
}
|
||
|
||
func (pg *Page) GetResList() ([]Resource, error) {
|
||
r, err := proto.PageGetResourceTree{}.Call(pg.longTimeoutPage)
|
||
if err != nil {
|
||
return nil, gojs.Err(err)
|
||
}
|
||
list := []Resource{}
|
||
for _, res := range r.FrameTree.Resources {
|
||
list = append(list, Resource{
|
||
Url: res.URL,
|
||
Type: string(res.Type),
|
||
Size: u.Int(res.ContentSize),
|
||
MimeType: res.MIMEType,
|
||
LastModified: res.LastModified.String(),
|
||
Success: !res.Failed && !res.Canceled,
|
||
})
|
||
}
|
||
return list, nil
|
||
}
|
||
|
||
func (pg *Page) GetResListByType(resType string) ([]Resource, error) {
|
||
list, err := pg.GetResList()
|
||
if err != nil {
|
||
return nil, err
|
||
}
|
||
result := []Resource{}
|
||
for _, res := range list {
|
||
if strings.EqualFold(res.Type, resType) {
|
||
result = append(result, res)
|
||
}
|
||
}
|
||
return result, nil
|
||
}
|
||
|
||
func (pg *Page) GetResListByMimeType(mimeType string) ([]Resource, error) {
|
||
list, err := pg.GetResList()
|
||
if err != nil {
|
||
return nil, err
|
||
}
|
||
isMatch := strings.HasSuffix(mimeType, "/*")
|
||
mimeType = strings.TrimSuffix(mimeType, "*")
|
||
result := []Resource{}
|
||
for _, res := range list {
|
||
if isMatch {
|
||
if strings.HasPrefix(res.MimeType, mimeType) {
|
||
result = append(result, res)
|
||
}
|
||
} else {
|
||
if strings.EqualFold(res.MimeType, mimeType) {
|
||
result = append(result, res)
|
||
}
|
||
}
|
||
}
|
||
return result, nil
|
||
}
|
||
|
||
func (pg *Page) GetResContent(url string) ([]byte, error) {
|
||
return pg.longTimeoutPage.GetResource(url)
|
||
}
|
||
|
||
func (pg *Page) Find(selector string) (*Element, error) {
|
||
el, err := pg.page.Element(selector)
|
||
if err != nil {
|
||
return nil, gojs.Err(err)
|
||
}
|
||
return &Element{element: el, page: pg.page}, nil
|
||
}
|
||
|
||
func (pg *Page) FindN(selector string) *Element {
|
||
els, err := pg.FindAll(selector)
|
||
if err != nil {
|
||
return nil
|
||
}
|
||
if len(els) == 0 {
|
||
return nil
|
||
}
|
||
return els[0]
|
||
}
|
||
|
||
func (pg *Page) FindAll(selector string) ([]*Element, error) {
|
||
elements, err := pg.page.Elements(selector)
|
||
if err != nil {
|
||
return nil, gojs.Err(err)
|
||
}
|
||
result := make([]*Element, len(elements))
|
||
for i, el := range elements {
|
||
result[i] = &Element{element: el, page: pg.page}
|
||
}
|
||
return result, nil
|
||
}
|
||
|
||
// FindX 通过XPath查找元素
|
||
func (pg *Page) FindX(xpath string) (*Element, error) {
|
||
el, err := pg.page.ElementX(xpath)
|
||
if err != nil {
|
||
return nil, gojs.Err(err)
|
||
}
|
||
return &Element{element: el, page: pg.page}, nil
|
||
}
|
||
|
||
func (pg *Page) FindXN(selector string) *Element {
|
||
el, err := pg.FindX(selector)
|
||
if err != nil {
|
||
return nil
|
||
}
|
||
return el
|
||
}
|
||
|
||
// FindsX 通过XPath查找多个元素
|
||
func (pg *Page) FindAllX(xpath string) ([]*Element, error) {
|
||
elements, err := pg.page.ElementsX(xpath)
|
||
if err != nil {
|
||
return nil, gojs.Err(err)
|
||
}
|
||
result := make([]*Element, len(elements))
|
||
for i, el := range elements {
|
||
result[i] = &Element{element: el, page: pg.page}
|
||
}
|
||
return result, nil
|
||
}
|
||
|
||
func (pg *Page) Navigate(url string) error {
|
||
err := pg.longTimeoutPage.Navigate(url)
|
||
if err != nil {
|
||
return gojs.Err(err)
|
||
}
|
||
pg.WaitPageLoad()
|
||
return nil
|
||
}
|
||
|
||
// WaitLoad 等待页面加载完成
|
||
func (pg *Page) WaitPageLoad() error {
|
||
time.Sleep(100 * time.Millisecond)
|
||
err := pg.page.WaitLoad()
|
||
if err != nil {
|
||
return gojs.Err(err)
|
||
}
|
||
pg.ResetTimeout()
|
||
return nil
|
||
}
|
||
|
||
// WaitIdle 等待页面空闲(无网络请求和JS执行)
|
||
func (pg *Page) WaitIdle(ms *int) error {
|
||
if ms == nil {
|
||
ms = u.IntPtr(1000)
|
||
}
|
||
return gojs.Err(pg.page.WaitIdle(time.Millisecond * time.Duration(*ms)))
|
||
}
|
||
|
||
// Wait 等待回调函数函数返回true
|
||
func (pg *Page) Wait(ms int, fn *func() bool) {
|
||
for i := 0; i < ms; i += 100 {
|
||
if fn != nil && (*fn)() {
|
||
break
|
||
}
|
||
time.Sleep(100 * time.Millisecond)
|
||
}
|
||
pg.ResetTimeout()
|
||
}
|
||
|
||
// RandWait 随机等待
|
||
func (pg *Page) RandWait(msMin int, msMax int) {
|
||
ms := u.GlobalRand1.Intn(msMax-msMin) + msMin
|
||
pg.Wait(ms, nil)
|
||
}
|
||
|
||
func (pg *Page) ResetTimeout() {
|
||
pg.page = pg.originPage.Timeout(time.Second * 5)
|
||
pg.longTimeoutPage = pg.originPage.Timeout(time.Second * 15)
|
||
}
|
||
|
||
// WaitStable 等待页面DOM稳定
|
||
// func (pg *Page) WaitStable(ms *int) error {
|
||
// if ms == nil {
|
||
// ms = u.IntPtr(100)
|
||
// }
|
||
// return gojs.Err(pg.page.WaitStable(time.Millisecond * time.Duration(*ms)))
|
||
// }
|
||
|
||
// Screenshot 截图
|
||
func (pg *Page) ScreenshotFullPage() ([]byte, error) {
|
||
buf, err := pg.longTimeoutPage.Screenshot(true, nil)
|
||
return buf, gojs.Err(err)
|
||
}
|
||
|
||
func (pg *Page) Screenshot() ([]byte, error) {
|
||
buf, err := pg.longTimeoutPage.Screenshot(false, nil)
|
||
return buf, gojs.Err(err)
|
||
}
|
||
|
||
// PDF 生成页面PDF
|
||
func (pg *Page) PDF() ([]byte, error) {
|
||
r, err := pg.longTimeoutPage.PDF(&proto.PagePrintToPDF{})
|
||
if err != nil {
|
||
return nil, gojs.Err(err)
|
||
}
|
||
bin, err := io.ReadAll(r)
|
||
if err != nil {
|
||
return nil, gojs.Err(err)
|
||
}
|
||
|
||
return bin, nil
|
||
}
|
||
|
||
func (pg *Page) Title() (string, error) {
|
||
info, err := pg.page.Info()
|
||
if err != nil {
|
||
return "", gojs.Err(err)
|
||
}
|
||
return info.Title, nil
|
||
}
|
||
|
||
func (pg *Page) Html() (string, error) {
|
||
html, err := pg.page.HTML()
|
||
return html, gojs.Err(err)
|
||
}
|
||
|
||
func (pg *Page) Url() (string, error) {
|
||
info, err := pg.page.Info()
|
||
if err != nil {
|
||
return "", gojs.Err(err)
|
||
}
|
||
return info.URL, nil
|
||
}
|
||
|
||
func (pg *Page) Eval(js string) (interface{}, error) {
|
||
r, err := pg.page.Eval(js)
|
||
if err != nil {
|
||
return nil, gojs.Err(err)
|
||
}
|
||
return r.Value.Val(), nil
|
||
}
|
||
|
||
func (pg *Page) MouseMoveTo(x, y float64) error {
|
||
return gojs.Err(pg.page.Mouse.MoveTo(proto.Point{X: x, Y: y}))
|
||
}
|
||
|
||
func (pg *Page) MouseClick() error {
|
||
return gojs.Err(pg.page.Mouse.Click(proto.InputMouseButtonLeft, 1))
|
||
}
|
||
|
||
func (pg *Page) MouseRightClick() error {
|
||
return gojs.Err(pg.page.Mouse.Click(proto.InputMouseButtonRight, 1))
|
||
}
|
||
|
||
func (pg *Page) MouseMiddleClick() error {
|
||
return gojs.Err(pg.page.Mouse.Click(proto.InputMouseButtonMiddle, 1))
|
||
}
|
||
|
||
func (pg *Page) MouseDown() error {
|
||
return gojs.Err(pg.page.Mouse.Down(proto.InputMouseButtonLeft, 1))
|
||
}
|
||
|
||
func (pg *Page) MouseUp() error {
|
||
return gojs.Err(pg.page.Mouse.Up(proto.InputMouseButtonLeft, 1))
|
||
}
|
||
|
||
func (pg *Page) SetCookies(cookies map[string]string) error {
|
||
cks := []*proto.NetworkCookieParam{}
|
||
for k, v := range cookies {
|
||
cks = append(cks, &proto.NetworkCookieParam{
|
||
Name: k,
|
||
Value: v,
|
||
})
|
||
}
|
||
err := pg.page.SetCookies(cks)
|
||
if err != nil {
|
||
return gojs.Err(err)
|
||
}
|
||
return nil
|
||
}
|
||
|
||
func (pg *Page) GetCookies() (map[string]string, error) {
|
||
cookies, err := pg.page.Cookies(nil)
|
||
if err != nil {
|
||
return nil, gojs.Err(err)
|
||
}
|
||
|
||
result := make(map[string]string)
|
||
for _, c := range cookies {
|
||
result[c.Name] = c.Value
|
||
}
|
||
return result, nil
|
||
}
|
||
|
||
// SetViewport 设置视口大小
|
||
func (pg *Page) SetViewport(width, height int) error {
|
||
return gojs.Err(pg.page.SetViewport(&proto.EmulationSetDeviceMetricsOverride{
|
||
Width: width,
|
||
Height: height,
|
||
DeviceScaleFactor: 0,
|
||
Mobile: false,
|
||
}))
|
||
}
|
||
|
||
func (pg *Page) SetPhoneViewport(width, height int) error {
|
||
return gojs.Err(pg.page.SetViewport(&proto.EmulationSetDeviceMetricsOverride{
|
||
Width: width,
|
||
Height: height,
|
||
DeviceScaleFactor: 0,
|
||
Mobile: true,
|
||
}))
|
||
}
|
||
|
||
// ScrollTo 滚动到指定位置
|
||
func (pg *Page) ScrollTo(x, y float64) error {
|
||
_, err := pg.page.Eval(`(x, y) => window.scrollTo(x, y)`, x, y)
|
||
return gojs.Err(err)
|
||
}
|
||
|
||
// Frame 获取iframe内容
|
||
func (pg *Page) Frame(selector string) (*Page, error) {
|
||
el, err := pg.page.Element(selector)
|
||
if err != nil {
|
||
return nil, gojs.Err(err)
|
||
}
|
||
p, err := el.Frame()
|
||
if err != nil {
|
||
return nil, gojs.Err(err)
|
||
}
|
||
return &Page{page: p}, nil
|
||
}
|
||
|
||
var isControlKey = map[string]bool{
|
||
"ShiftLeft": true,
|
||
"ShiftRight": true,
|
||
"Shift": true,
|
||
"ControlLeft": true,
|
||
"ControlRight": true,
|
||
"Ctrl": true,
|
||
"Control": true,
|
||
"MetaLeft": true,
|
||
"MetaRight": true,
|
||
"Meta": true,
|
||
"Cmd": true,
|
||
"Windows": true,
|
||
"Win": true,
|
||
"AltLeft": true,
|
||
"AltRight": true,
|
||
"AltGraph": true,
|
||
"Option": true,
|
||
"Alt": true,
|
||
}
|
||
|
||
var KeyMap = map[string]input.Key{
|
||
"Escape": input.Escape,
|
||
"Esc": input.Escape,
|
||
"F1": input.F1,
|
||
"F2": input.F2,
|
||
"F3": input.F3,
|
||
"F4": input.F4,
|
||
"F5": input.F5,
|
||
"F6": input.F6,
|
||
"F7": input.F7,
|
||
"F8": input.F8,
|
||
"F9": input.F9,
|
||
"F10": input.F10,
|
||
"F11": input.F11,
|
||
"F12": input.F12,
|
||
"Backquote": input.Backquote,
|
||
"1": input.Digit1,
|
||
"2": input.Digit2,
|
||
"3": input.Digit3,
|
||
"4": input.Digit4,
|
||
"5": input.Digit5,
|
||
"6": input.Digit6,
|
||
"7": input.Digit7,
|
||
"8": input.Digit8,
|
||
"9": input.Digit9,
|
||
"0": input.Digit0,
|
||
"Digit1": input.Digit1,
|
||
"Digit2": input.Digit2,
|
||
"Digit3": input.Digit3,
|
||
"Digit4": input.Digit4,
|
||
"Digit5": input.Digit5,
|
||
"Digit6": input.Digit6,
|
||
"Digit7": input.Digit7,
|
||
"Digit8": input.Digit8,
|
||
"Digit9": input.Digit9,
|
||
"Digit0": input.Digit0,
|
||
"Minus": input.Minus,
|
||
"Equal": input.Equal,
|
||
"Backslash": input.Backslash,
|
||
"Backspace": input.Backspace,
|
||
"Tab": input.Tab,
|
||
"KeyQ": input.KeyQ,
|
||
"Q": input.KeyQ,
|
||
"KeyW": input.KeyW,
|
||
"W": input.KeyW,
|
||
"KeyE": input.KeyE,
|
||
"E": input.KeyE,
|
||
"KeyR": input.KeyR,
|
||
"R": input.KeyR,
|
||
"KeyT": input.KeyT,
|
||
"T": input.KeyT,
|
||
"KeyY": input.KeyY,
|
||
"Y": input.KeyY,
|
||
"KeyU": input.KeyU,
|
||
"U": input.KeyU,
|
||
"KeyI": input.KeyI,
|
||
"I": input.KeyI,
|
||
"KeyO": input.KeyO,
|
||
"O": input.KeyO,
|
||
"KeyP": input.KeyP,
|
||
"P": input.KeyP,
|
||
"BracketLeft": input.BracketLeft,
|
||
"BracketRight": input.BracketRight,
|
||
"CapsLock": input.CapsLock,
|
||
"KeyA": input.KeyA,
|
||
"A": input.KeyA,
|
||
"KeyS": input.KeyS,
|
||
"S": input.KeyS,
|
||
"KeyD": input.KeyD,
|
||
"D": input.KeyD,
|
||
"KeyF": input.KeyF,
|
||
"F": input.KeyF,
|
||
"KeyG": input.KeyG,
|
||
"G": input.KeyG,
|
||
"KeyH": input.KeyH,
|
||
"H": input.KeyH,
|
||
"KeyJ": input.KeyJ,
|
||
"J": input.KeyJ,
|
||
"KeyK": input.KeyK,
|
||
"K": input.KeyK,
|
||
"KeyL": input.KeyL,
|
||
"L": input.KeyL,
|
||
"Semicolon": input.Semicolon,
|
||
"Quote": input.Quote,
|
||
"Enter": input.Enter,
|
||
"ShiftLeft": input.ShiftLeft,
|
||
"KeyZ": input.KeyZ,
|
||
"Z": input.KeyZ,
|
||
"KeyX": input.KeyX,
|
||
"X": input.KeyX,
|
||
"KeyC": input.KeyC,
|
||
"C": input.KeyC,
|
||
"KeyV": input.KeyV,
|
||
"V": input.KeyV,
|
||
"KeyB": input.KeyB,
|
||
"B": input.KeyB,
|
||
"KeyN": input.KeyN,
|
||
"N": input.KeyN,
|
||
"KeyM": input.KeyM,
|
||
"M": input.KeyM,
|
||
"Comma": input.Comma,
|
||
"Period": input.Period,
|
||
"Slash": input.Slash,
|
||
"ShiftRight": input.ShiftRight,
|
||
"ControlLeft": input.ControlLeft,
|
||
"MetaLeft": input.MetaLeft,
|
||
"AltLeft": input.AltLeft,
|
||
"Space": input.Space,
|
||
" ": input.Space,
|
||
"AltRight": input.AltRight,
|
||
"AltGraph": input.AltGraph,
|
||
"MetaRight": input.MetaRight,
|
||
"ContextMenu": input.ContextMenu,
|
||
"ControlRight": input.ControlRight,
|
||
"PrintScreen": input.PrintScreen,
|
||
"ScrollLock": input.ScrollLock,
|
||
"Pause": input.Pause,
|
||
"PageUp": input.PageUp,
|
||
"PageDown": input.PageDown,
|
||
"Insert": input.Insert,
|
||
"Delete": input.Delete,
|
||
"Home": input.Home,
|
||
"End": input.End,
|
||
"ArrowLeft": input.ArrowLeft,
|
||
"ArrowUp": input.ArrowUp,
|
||
"ArrowRight": input.ArrowRight,
|
||
"ArrowDown": input.ArrowDown,
|
||
"Left": input.ArrowLeft,
|
||
"Up": input.ArrowUp,
|
||
"Right": input.ArrowRight,
|
||
"Down": input.ArrowDown,
|
||
"NumLock": input.NumLock,
|
||
"NumpadDivide": input.NumpadDivide,
|
||
"NumpadMultiply": input.NumpadMultiply,
|
||
"NumpadSubtract": input.NumpadSubtract,
|
||
"Numpad7": input.Numpad7,
|
||
"Numpad8": input.Numpad8,
|
||
"Numpad9": input.Numpad9,
|
||
"Numpad4": input.Numpad4,
|
||
"Numpad5": input.Numpad5,
|
||
"Numpad6": input.Numpad6,
|
||
"NumpadAdd": input.NumpadAdd,
|
||
"Numpad1": input.Numpad1,
|
||
"Numpad2": input.Numpad2,
|
||
"Numpad3": input.Numpad3,
|
||
"Numpad0": input.Numpad0,
|
||
"NumpadDecimal": input.NumpadDecimal,
|
||
"NumpadEnter": input.NumpadEnter,
|
||
"NumDivide": input.NumpadDivide,
|
||
"NumMultiply": input.NumpadMultiply,
|
||
"NumSubtract": input.NumpadSubtract,
|
||
"Num7": input.Numpad7,
|
||
"Num8": input.Numpad8,
|
||
"Num9": input.Numpad9,
|
||
"Num4": input.Numpad4,
|
||
"Num5": input.Numpad5,
|
||
"Num6": input.Numpad6,
|
||
"NumAdd": input.NumpadAdd,
|
||
"Num1": input.Numpad1,
|
||
"Num2": input.Numpad2,
|
||
"Num3": input.Numpad3,
|
||
"Num0": input.Numpad0,
|
||
"NumDecimal": input.NumpadDecimal,
|
||
"NumEnter": input.NumpadEnter,
|
||
"Ctrl": input.ControlLeft,
|
||
"Control": input.ControlLeft,
|
||
"Cmd": input.MetaLeft,
|
||
"Windows": input.MetaLeft,
|
||
"Meta": input.MetaLeft,
|
||
"Win": input.MetaLeft,
|
||
"Option": input.AltLeft,
|
||
"Alt": input.AltLeft,
|
||
",": input.Comma,
|
||
".": input.Period,
|
||
"/": input.Slash,
|
||
";": input.Semicolon,
|
||
"'": input.Quote,
|
||
"[": input.BracketLeft,
|
||
"]": input.BracketRight,
|
||
"\\": input.Backslash,
|
||
"-": input.Minus,
|
||
"=": input.Equal,
|
||
"`": input.Backquote,
|
||
}
|
||
|
||
func keyFilter(controlKey bool, keys ...string) []string {
|
||
var filteredKeys []string
|
||
for _, key := range keys {
|
||
if (controlKey && isControlKey[key]) || (!controlKey && !isControlKey[key]) {
|
||
filteredKeys = append(filteredKeys, key)
|
||
}
|
||
}
|
||
return filteredKeys
|
||
}
|
||
|
||
func keyAction(page *rod.Page, action string, keys ...string) error {
|
||
for _, key := range keys {
|
||
if k, ok := KeyMap[key]; ok {
|
||
var err error
|
||
switch action {
|
||
case "press":
|
||
err = page.Keyboard.Press(k)
|
||
case "release":
|
||
err = page.Keyboard.Release(k)
|
||
case "type":
|
||
err = page.Keyboard.Type(k)
|
||
}
|
||
if err != nil {
|
||
return gojs.Err(err)
|
||
}
|
||
}
|
||
}
|
||
return nil
|
||
}
|
||
|
||
func (pg *Page) KeyPress(keys ...string) error {
|
||
return keyAction(pg.page, "press", keys...)
|
||
}
|
||
|
||
func (pg *Page) KeyRelease(keys ...string) error {
|
||
return keyAction(pg.page, "release", keys...)
|
||
}
|
||
|
||
func (pg *Page) KeyType(keys ...string) error {
|
||
return keyAction(pg.page, "type", keys...)
|
||
}
|
||
|
||
func (pg *Page) Key(keys ...string) error {
|
||
controlKeys := keyFilter(true, keys...)
|
||
// 按下所有控制键
|
||
err := keyAction(pg.page, "press", controlKeys...)
|
||
if err == nil {
|
||
// 输入所有非控制键
|
||
err = keyAction(pg.page, "type", keyFilter(false, keys...)...)
|
||
}
|
||
if err == nil {
|
||
// 释放所有控制键
|
||
err = keyAction(pg.page, "release", controlKeys...)
|
||
}
|
||
return err
|
||
}
|
||
|
||
func (pg *Page) SetUserAgent(ua string) error {
|
||
return gojs.Err(pg.page.SetUserAgent(&proto.NetworkSetUserAgentOverride{
|
||
UserAgent: ua,
|
||
}))
|
||
}
|
||
|
||
func (pg *Page) SetUserAgentInfo(ua string, platform string, acceptLanguage string) error {
|
||
return gojs.Err(pg.page.SetUserAgent(&proto.NetworkSetUserAgentOverride{
|
||
UserAgent: ua,
|
||
Platform: platform,
|
||
AcceptLanguage: acceptLanguage,
|
||
}))
|
||
}
|
||
|
||
func (pg *Page) Back() error {
|
||
err := pg.page.NavigateBack()
|
||
if err != nil {
|
||
return gojs.Err(err)
|
||
}
|
||
pg.WaitPageLoad()
|
||
return nil
|
||
}
|
||
|
||
func (pg *Page) Forward() error {
|
||
err := pg.page.NavigateForward()
|
||
if err != nil {
|
||
return gojs.Err(err)
|
||
}
|
||
pg.WaitPageLoad()
|
||
return nil
|
||
}
|
||
|
||
// ================== 对话框控制 ==================
|
||
|
||
// HandleDialog 设置弹框处理器
|
||
// 示例:pg.HandleDialog(true, "") 接受所有弹框
|
||
func (pg *Page) HandleDialog(accept bool, promptText string) error {
|
||
wait, handle := pg.longTimeoutPage.HandleDialog()
|
||
wait()
|
||
return gojs.Err(handle(&proto.PageHandleJavaScriptDialog{
|
||
Accept: accept,
|
||
PromptText: promptText,
|
||
}))
|
||
}
|
||
|
||
// AutoAcceptAlerts 自动接受所有弹框
|
||
func (pg *Page) AccepDialog() error {
|
||
return gojs.Err(pg.HandleDialog(true, ""))
|
||
}
|
||
|
||
// AutoDismissAlerts 自动取消所有弹框
|
||
func (pg *Page) DismissDialog() error {
|
||
return gojs.Err(pg.HandleDialog(false, ""))
|
||
}
|
||
|
||
func (pg *Page) SetPrompt(text string) error {
|
||
return gojs.Err(pg.HandleDialog(true, text))
|
||
}
|
||
|
||
// ================== Element 方法 ==================
|
||
|
||
func (el *Element) Click() error {
|
||
return gojs.Err(el.element.Click(proto.InputMouseButtonLeft, 1))
|
||
}
|
||
|
||
func (el *Element) Input(text string) error {
|
||
return gojs.Err(el.element.Input(text))
|
||
}
|
||
|
||
func (el *Element) Text() (string, error) {
|
||
text, err := el.element.Text()
|
||
return text, gojs.Err(err)
|
||
}
|
||
|
||
func (el *Element) InnerHTML() (string, error) {
|
||
r, err := el.element.Eval(`el => {return el.innerHTML}`)
|
||
if err != nil {
|
||
return "", gojs.Err(err)
|
||
}
|
||
return r.Value.String(), nil
|
||
}
|
||
|
||
func (el *Element) OuterHTML() (string, error) {
|
||
html, err := el.element.HTML()
|
||
return html, gojs.Err(err)
|
||
}
|
||
|
||
func (el *Element) SetInnerHTML(html string) error {
|
||
_, err := el.element.Eval(`(el, html) => el.innerHTML = html`, html)
|
||
return gojs.Err(err)
|
||
}
|
||
|
||
func (el *Element) SetText(text string) error {
|
||
_, err := el.element.Eval(`(el, text) => el.innerText = text`, text)
|
||
return gojs.Err(err)
|
||
}
|
||
|
||
// ----------
|
||
|
||
func (el *Element) WaitVisible() error {
|
||
return gojs.Err(el.element.WaitVisible())
|
||
}
|
||
|
||
func (el *Element) WaitInvisible() error {
|
||
return gojs.Err(el.element.WaitInvisible())
|
||
}
|
||
|
||
func (el *Element) WaitEnabled() error {
|
||
return gojs.Err(el.element.WaitEnabled())
|
||
}
|
||
|
||
func (el *Element) Focus() error {
|
||
return gojs.Err(el.element.Focus())
|
||
}
|
||
|
||
func (el *Element) ScrollIntoView() error {
|
||
return gojs.Err(el.element.ScrollIntoView())
|
||
}
|
||
|
||
func (el *Element) UploadFile(filePaths []string) error {
|
||
return gojs.Err(el.element.SetFiles(filePaths))
|
||
}
|
||
|
||
func (el *Element) Hover() error {
|
||
return gojs.Err(el.element.Hover())
|
||
}
|
||
|
||
// Select 选择下拉框的选项(单选或多选)
|
||
func (el *Element) Select(values ...string) error {
|
||
if len(values) == 0 {
|
||
return nil
|
||
}
|
||
return gojs.Err(el.element.Select(values, true, rod.SelectorTypeText))
|
||
}
|
||
|
||
// GetSelectedValues 获取当前选中的值(单选返回字符串,多选返回切片)
|
||
func (el *Element) GetChecked() ([]string, error) {
|
||
selected, err := el.element.Elements(":checked")
|
||
if err != nil {
|
||
return nil, gojs.Err(err)
|
||
}
|
||
values := make([]string, len(selected))
|
||
for i, opt := range selected {
|
||
val, err := opt.Attribute("value")
|
||
if err != nil {
|
||
return nil, gojs.Err(err)
|
||
}
|
||
values[i] = u.String(val)
|
||
}
|
||
return values, nil
|
||
}
|
||
|
||
// GetAllOptions 获取所有选项(值和文本)
|
||
func (el *Element) GetOptions() ([]map[string]string, error) {
|
||
options, err := el.element.Elements("option")
|
||
if err != nil {
|
||
return nil, gojs.Err(err)
|
||
}
|
||
result := make([]map[string]string, len(options))
|
||
for i, opt := range options {
|
||
val, err := opt.Attribute("value")
|
||
if err != nil {
|
||
return nil, gojs.Err(err)
|
||
}
|
||
text, err := opt.Text()
|
||
if err != nil {
|
||
return nil, gojs.Err(err)
|
||
}
|
||
result[i] = map[string]string{
|
||
"value": u.String(val),
|
||
"text": text,
|
||
}
|
||
}
|
||
return result, nil
|
||
}
|
||
|
||
// RadioGroupValue 获取单选按钮组的当前值
|
||
func (el *Element) RadioGroupValue() (string, error) {
|
||
name, err := el.GetAttribute("name")
|
||
if err != nil {
|
||
return "", gojs.Err(err)
|
||
}
|
||
if name == nil {
|
||
return "", nil
|
||
}
|
||
|
||
checked, err := el.page.ElementX(fmt.Sprintf("input[type='radio'][name='%s']:checked", *name))
|
||
if err != nil {
|
||
return "", gojs.Err(err)
|
||
}
|
||
if checked == nil {
|
||
return "", nil
|
||
}
|
||
val, err := checked.Attribute("value")
|
||
if err != nil {
|
||
return "", gojs.Err(err)
|
||
}
|
||
return u.String(val), nil
|
||
}
|
||
|
||
// SetRadioGroupValue 设置单选按钮组的值
|
||
func (el *Element) SetRadioGroupValue(value string) error {
|
||
name, err := el.GetAttribute("name")
|
||
if err != nil {
|
||
return gojs.Err(err)
|
||
}
|
||
if name == nil {
|
||
return nil
|
||
}
|
||
|
||
radio, err := el.page.ElementX(fmt.Sprintf("input[type='radio'][name='%s'][value='%s']", *name, value))
|
||
if err != nil {
|
||
return gojs.Err(err)
|
||
}
|
||
return gojs.Err(radio.Click(proto.InputMouseButtonLeft, 1))
|
||
}
|
||
|
||
// SelectByValue 通过值选择选项
|
||
func (el *Element) SelectByValue(value string) error {
|
||
opt, err := el.element.ElementX(fmt.Sprintf(".//option[@value='%s']", value))
|
||
if err != nil {
|
||
return gojs.Err(err)
|
||
}
|
||
return gojs.Err(opt.Click(proto.InputMouseButtonLeft, 1))
|
||
}
|
||
|
||
// SelectByText 通过文本选择选项
|
||
func (el *Element) SelectByText(text string) error {
|
||
opt, err := el.element.ElementX(fmt.Sprintf(".//option[normalize-space()='%s']", text))
|
||
if err != nil {
|
||
return gojs.Err(err)
|
||
}
|
||
return gojs.Err(opt.Click(proto.InputMouseButtonLeft, 1))
|
||
}
|
||
|
||
// SetRadioValue 设置单选框值
|
||
func (el *Element) SetRadioValue(value string) error {
|
||
radio, err := el.element.ElementX(fmt.Sprintf(".//input[@type='radio'][@value='%s']", value))
|
||
if err != nil {
|
||
return gojs.Err(err)
|
||
}
|
||
return gojs.Err(radio.Click(proto.InputMouseButtonLeft, 1))
|
||
}
|
||
|
||
// GetForm 获取当前元素所属的表单
|
||
func (el *Element) GetForm() (*Element, error) {
|
||
form, err := el.element.ElementX("ancestor::form")
|
||
if err != nil {
|
||
return nil, gojs.Err(err)
|
||
}
|
||
return &Element{element: form, page: el.page}, nil
|
||
}
|
||
|
||
// GetFormData 获取表单数据
|
||
func (el *Element) GetFormData() (map[string]string, error) {
|
||
r, err := el.element.Eval(`form => {
|
||
const formData = new FormData(form);
|
||
return Object.fromEntries(formData.entries());
|
||
}`)
|
||
if err != nil {
|
||
return nil, gojs.Err(err)
|
||
}
|
||
data := r.Value.Map()
|
||
result := make(map[string]string)
|
||
for k, v := range data {
|
||
result[k] = v.String()
|
||
}
|
||
return result, nil
|
||
}
|
||
|
||
// SetValue 设置表单元素的值(input/textarea)
|
||
func (el *Element) SetValue(value string) error {
|
||
// _, err := el.element.Eval(`(el, v) => { el.value = v }`, value)
|
||
err := el.element.Input(value)
|
||
if err == nil {
|
||
return nil
|
||
}
|
||
el.WaitStable(nil)
|
||
return nil
|
||
}
|
||
|
||
// GetValue 获取表单元素的值
|
||
func (el *Element) GetValue() (string, error) {
|
||
prop, err := el.element.Property("value")
|
||
if err != nil {
|
||
return "", gojs.Err(err)
|
||
}
|
||
return prop.Str(), nil
|
||
}
|
||
|
||
// SetChecked 设置复选框/单选框的选中状态
|
||
func (el *Element) SetChecked(checked bool) error {
|
||
current, err := el.IsChecked()
|
||
if err != nil {
|
||
return gojs.Err(err)
|
||
}
|
||
if current != checked {
|
||
return gojs.Err(el.element.Click(proto.InputMouseButtonLeft, 1))
|
||
}
|
||
return nil
|
||
}
|
||
|
||
// IsChecked 获取复选框/单选框的选中状态
|
||
func (el *Element) IsChecked() (bool, error) {
|
||
prop, err := el.element.Property("checked")
|
||
if err != nil {
|
||
return false, gojs.Err(err)
|
||
}
|
||
return prop.Bool(), nil
|
||
}
|
||
|
||
// IsDisabled 判断元素是否被禁用
|
||
func (el *Element) IsDisabled() (bool, error) {
|
||
prop, err := el.element.Property("disabled")
|
||
if err != nil {
|
||
return false, gojs.Err(err)
|
||
}
|
||
return prop.Bool(), nil
|
||
}
|
||
|
||
const maxParentDepth = 100 // 防止无限递归
|
||
func findParentRecursive(el *rod.Element, selector string, depth int) (*rod.Element, error) {
|
||
if depth > maxParentDepth {
|
||
return nil, gojs.Err(fmt.Errorf("max recursion depth (%d) reached", maxParentDepth))
|
||
}
|
||
|
||
// 获取直接父元素
|
||
parent, err := el.Parent()
|
||
if err != nil || parent == nil {
|
||
return nil, gojs.Err(err)
|
||
}
|
||
|
||
// 检查是否匹配选择器
|
||
matches, err := parent.Matches(selector)
|
||
if err != nil {
|
||
return nil, gojs.Err(err)
|
||
}
|
||
|
||
if matches {
|
||
return parent, nil
|
||
}
|
||
|
||
// 继续向上递归查找
|
||
return findParentRecursive(parent, selector, depth+1)
|
||
}
|
||
|
||
// FindParent 向上查找匹配选择器的父元素
|
||
func (el *Element) FindParent(selector string) (*Element, error) {
|
||
parent, err := findParentRecursive(el.element, selector, 0)
|
||
if err != nil {
|
||
return nil, gojs.Err(err)
|
||
}
|
||
return &Element{element: parent, page: el.page}, nil
|
||
}
|
||
|
||
func (el *Element) FindParentN(selector string) *Element {
|
||
el, err := el.FindParent(selector)
|
||
if err != nil {
|
||
return nil
|
||
}
|
||
return el
|
||
}
|
||
|
||
// FindParent 向上查找匹配选择器的父元素
|
||
func (el *Element) FindParentX(selector string) (*Element, error) {
|
||
parent, err := el.element.ElementX(fmt.Sprintf("ancestor::%s", selector))
|
||
if err != nil {
|
||
return nil, gojs.Err(err)
|
||
}
|
||
return &Element{element: parent, page: el.page}, nil
|
||
}
|
||
|
||
func (el *Element) FindParentXN(selector string) *Element {
|
||
el, err := el.FindParentX(selector)
|
||
if err != nil {
|
||
return nil
|
||
}
|
||
return el
|
||
}
|
||
|
||
// Parent 获取直接父元素
|
||
func (el *Element) Parent() (*Element, error) {
|
||
parent, err := el.element.ElementX("..")
|
||
if err != nil {
|
||
return nil, gojs.Err(err)
|
||
}
|
||
return &Element{element: parent, page: el.page}, nil
|
||
}
|
||
|
||
// Children 获取所有直接子元素
|
||
func (el *Element) Children() ([]*Element, error) {
|
||
children, err := el.element.ElementsX("./*")
|
||
if err != nil {
|
||
return nil, gojs.Err(err)
|
||
}
|
||
result := make([]*Element, len(children))
|
||
for i, c := range children {
|
||
result[i] = &Element{element: c, page: el.page}
|
||
}
|
||
return result, nil
|
||
}
|
||
|
||
// Find 在当前元素下查找匹配选择器的第一个元素
|
||
func (el *Element) FindChild(selector string) (*Element, error) {
|
||
elem, err := el.element.Element(":scope > " + selector)
|
||
if err != nil {
|
||
return nil, gojs.Err(err)
|
||
}
|
||
return &Element{element: elem, page: el.page}, nil
|
||
}
|
||
|
||
func (el *Element) FindChildN(selector string) *Element {
|
||
el, err := el.FindChild(selector)
|
||
if err != nil {
|
||
return nil
|
||
}
|
||
return el
|
||
}
|
||
|
||
// FindAll 在当前元素下查找所有匹配选择器的元素
|
||
func (el *Element) FindChildren(selector string) ([]*Element, error) {
|
||
elements, err := el.element.Elements(":scope > " + selector)
|
||
if err != nil {
|
||
return nil, gojs.Err(err)
|
||
}
|
||
result := make([]*Element, len(elements))
|
||
for i, e := range elements {
|
||
result[i] = &Element{element: e, page: el.page}
|
||
}
|
||
return result, nil
|
||
}
|
||
|
||
// Find 在当前元素下查找匹配选择器的第一个元素
|
||
func (el *Element) Find(selector string) (*Element, error) {
|
||
elem, err := el.element.Element(selector)
|
||
if err != nil {
|
||
return nil, gojs.Err(err)
|
||
}
|
||
return &Element{element: elem, page: el.page}, nil
|
||
}
|
||
|
||
func (el *Element) FindN(selector string) *Element {
|
||
el, err := el.Find(selector)
|
||
if err != nil {
|
||
return nil
|
||
}
|
||
return el
|
||
}
|
||
|
||
// FindAll 在当前元素下查找所有匹配选择器的元素
|
||
func (el *Element) FindAll(selector string) ([]*Element, error) {
|
||
elements, err := el.element.Elements(selector)
|
||
if err != nil {
|
||
return nil, gojs.Err(err)
|
||
}
|
||
result := make([]*Element, len(elements))
|
||
for i, e := range elements {
|
||
result[i] = &Element{element: e, page: el.page}
|
||
}
|
||
return result, nil
|
||
}
|
||
|
||
// Find 在当前元素下查找匹配选择器的第一个元素
|
||
func (el *Element) FindX(selector string) (*Element, error) {
|
||
elem, err := el.element.ElementX(selector)
|
||
if err != nil {
|
||
return nil, gojs.Err(err)
|
||
}
|
||
return &Element{element: elem, page: el.page}, nil
|
||
}
|
||
|
||
func (el *Element) FindXN(selector string) *Element {
|
||
el, err := el.FindX(selector)
|
||
if err != nil {
|
||
return nil
|
||
}
|
||
return el
|
||
}
|
||
|
||
// FindAll 在当前元素下查找所有匹配选择器的元素
|
||
func (el *Element) FindAllX(selector string) ([]*Element, error) {
|
||
elements, err := el.element.ElementsX(selector)
|
||
if err != nil {
|
||
return nil, gojs.Err(err)
|
||
}
|
||
result := make([]*Element, len(elements))
|
||
for i, e := range elements {
|
||
result[i] = &Element{element: e, page: el.page}
|
||
}
|
||
return result, nil
|
||
}
|
||
|
||
// Submit 提交表单(必须在FORM元素上调用)
|
||
func (el *Element) Submit() error {
|
||
tagName, err := el.element.Eval("el => el.tagName")
|
||
if err != nil {
|
||
return gojs.Err(err)
|
||
}
|
||
if !strings.EqualFold(tagName.Value.String(), "FORM") {
|
||
return gojs.Err(fmt.Errorf("Submit can only be called on a FORM element"))
|
||
}
|
||
_, err = el.element.Eval("form => form.submit()")
|
||
return gojs.Err(err)
|
||
}
|
||
|
||
// Check 勾选复选框
|
||
func (el *Element) Check() error {
|
||
checked, err := el.IsChecked()
|
||
if err != nil {
|
||
return gojs.Err(err)
|
||
}
|
||
if !checked {
|
||
return gojs.Err(el.element.Click(proto.InputMouseButtonLeft, 1))
|
||
}
|
||
return nil
|
||
}
|
||
|
||
// Uncheck 取消勾选
|
||
func (el *Element) Uncheck() error {
|
||
checked, err := el.IsChecked()
|
||
if err != nil {
|
||
return gojs.Err(err)
|
||
}
|
||
if checked {
|
||
return gojs.Err(el.element.Click(proto.InputMouseButtonLeft, 1))
|
||
}
|
||
return nil
|
||
}
|
||
|
||
func (el *Element) GetBoundingRect() (*Rect, error) {
|
||
shape, err := el.element.Shape()
|
||
if err != nil {
|
||
return nil, gojs.Err(err)
|
||
}
|
||
|
||
if box := shape.Box(); box != nil {
|
||
return &Rect{X: box.X, Y: box.Y, Width: box.Width, Height: box.Height}, nil
|
||
}
|
||
|
||
return &Rect{}, nil
|
||
}
|
||
|
||
func (el *Element) GetCenter() (*Position, error) {
|
||
rect, err := el.GetBoundingRect()
|
||
if err != nil {
|
||
return nil, gojs.Err(err)
|
||
}
|
||
return &Position{X: rect.X + rect.Width/2, Y: rect.Y + rect.Height/2}, nil
|
||
}
|
||
|
||
// GetName 获取元素名称(如<input name="username">)
|
||
func (el *Element) GetName() (*string, error) {
|
||
return el.GetAttribute("name")
|
||
}
|
||
|
||
// GetID 获取元素ID
|
||
func (el *Element) GetID() (*string, error) {
|
||
return el.GetAttribute("id")
|
||
}
|
||
|
||
// GetType 获取元素类型(如"text"/"checkbox"等)
|
||
func (el *Element) GetType() (*string, error) {
|
||
return el.GetAttribute("type")
|
||
}
|
||
|
||
func (el *Element) AddClass(className string) error {
|
||
_, err := el.element.Eval(`(el, className) => {
|
||
if (!el) return;
|
||
if (el.classList) {
|
||
el.classList.add(className);
|
||
} else if (el.className) {
|
||
const classes = el.className.split(' ');
|
||
if (!classes.includes(className)) {
|
||
classes.push(className);
|
||
el.className = classes.join(' ').trim();
|
||
}
|
||
}
|
||
}`, className)
|
||
return gojs.Err(err)
|
||
}
|
||
|
||
func (el *Element) GetClass() ([]string, error) {
|
||
r, err := el.element.Property("className")
|
||
if err != nil {
|
||
return nil, gojs.Err(err)
|
||
}
|
||
return u.SplitWithoutNone(r.Str(), " "), nil
|
||
}
|
||
|
||
func (el *Element) RemoveClass(className string) error {
|
||
_, err := el.element.Eval(`(el, className) => {
|
||
if (!el) return;
|
||
if (el.classList) {
|
||
el.classList.remove(className);
|
||
} else if (el.className) {
|
||
const classes = el.className.split(' ');
|
||
const index = classes.indexOf(className);
|
||
if (index !== -1) {
|
||
classes.splice(index, 1);
|
||
el.className = classes.join(' ').trim();
|
||
}
|
||
}
|
||
}`, className)
|
||
return gojs.Err(err)
|
||
}
|
||
|
||
// GetStyle 获取指定CSS样式值
|
||
func (el *Element) GetStyle(property string) (string, error) {
|
||
r, err := el.element.Eval(`(el, prop) =>
|
||
window.getComputedStyle(el).getPropertyValue(prop)`,
|
||
property)
|
||
if err != nil {
|
||
return "", gojs.Err(err)
|
||
}
|
||
return r.Value.Str(), nil
|
||
}
|
||
|
||
// SetStyle 设置CSS样式
|
||
func (el *Element) SetStyle(property, value string) error {
|
||
_, err := el.element.Eval(`(el, prop, val) => el.style.setProperty(prop, val)`, property, value)
|
||
return gojs.Err(err)
|
||
}
|
||
|
||
// SetAttribute 设置元素属性值
|
||
// name: 属性名称 value: 属性值
|
||
func (el *Element) SetAttribute(name, value string) error {
|
||
_, err := el.element.Eval(`(e, n, v) => e.setAttribute(n, v)`, name, value)
|
||
return gojs.Err(err)
|
||
}
|
||
|
||
// HasAttribute 检查属性是否存在
|
||
func (el *Element) HasAttribute(name string) (bool, error) {
|
||
r, err := el.element.Eval(`(el, name) => el.hasAttribute(name)`, name)
|
||
if err != nil {
|
||
return false, gojs.Err(err)
|
||
}
|
||
return r.Value.Bool(), nil
|
||
}
|
||
|
||
// GetAttribute 获取元素属性值
|
||
// name: 属性名称
|
||
// 返回值: 属性值字符串,若不存在则返回空字符串
|
||
func (el *Element) GetAttribute(name string) (*string, error) {
|
||
val, err := el.element.Attribute(name)
|
||
return val, gojs.Err(err)
|
||
}
|
||
|
||
// RemoveAttribute 移除元素属性
|
||
// name: 要移除的属性名称
|
||
func (el *Element) RemoveAttribute(name string) error {
|
||
_, err := el.element.Eval(`(e, n) => e.removeAttribute(n)`, name)
|
||
return gojs.Err(err)
|
||
}
|
||
|
||
// SetProperty 设置 JavaScript 属性
|
||
// name: 属性名称 value: 属性值(可以是任意类型)
|
||
func (el *Element) SetProperty(name string, value interface{}) error {
|
||
_, err := el.element.Eval(`(el, n, v) => el[n] = v`, name, value)
|
||
return gojs.Err(err)
|
||
}
|
||
|
||
// GetProperty 获取 JavaScript 属性值
|
||
// name: 属性名称
|
||
// 返回值: 属性值(interface{} 类型)
|
||
func (el *Element) GetProperty(name string) (interface{}, error) {
|
||
prop, err := el.element.Property(name)
|
||
if err != nil {
|
||
return nil, gojs.Err(err)
|
||
}
|
||
return prop.Val(), nil
|
||
}
|
||
|
||
// HasProperty 检查属性是否存在
|
||
func (el *Element) HasProperty(name string) (bool, error) {
|
||
r, err := el.element.Eval(`(el, name) => el.hasOwnProperty(name)`, name)
|
||
if err != nil {
|
||
return false, gojs.Err(err)
|
||
}
|
||
return r.Value.Bool(), nil
|
||
}
|
||
|
||
func (el *Element) RemoveProperty(name string) error {
|
||
_, err := el.element.Eval(`(el, name) => delete el[name]`, name)
|
||
return gojs.Err(err)
|
||
}
|
||
|
||
// IsInteractable 元素是否可交互
|
||
func (el *Element) IsInteractable() (bool, error) {
|
||
_, err := el.element.Interactable()
|
||
if errors.Is(err, &rod.NotInteractableError{}) {
|
||
return false, nil
|
||
}
|
||
if err != nil {
|
||
return false, gojs.Err(err)
|
||
}
|
||
return true, nil
|
||
}
|
||
|
||
func (el *Element) WaitStable(ms *int) error {
|
||
if ms == nil {
|
||
ms = u.IntPtr(100)
|
||
}
|
||
return gojs.Err(el.element.Timeout(time.Second).WaitStable(time.Millisecond * time.Duration(*ms)))
|
||
}
|
||
|
||
// 元素级
|
||
func (el *Element) KeyPress(keys ...string) error {
|
||
if err := el.element.Focus(); err != nil {
|
||
return gojs.Err(err)
|
||
}
|
||
return keyAction(el.page, "press", keys...)
|
||
}
|
||
|
||
func (el *Element) KeyRelease(keys ...string) error {
|
||
if err := el.element.Focus(); err != nil {
|
||
return gojs.Err(err)
|
||
}
|
||
return keyAction(el.page, "release", keys...)
|
||
}
|
||
|
||
func (el *Element) KeyType(keys ...string) error {
|
||
if err := el.element.Focus(); err != nil {
|
||
return gojs.Err(err)
|
||
}
|
||
return keyAction(el.page, "type", keys...)
|
||
}
|
||
|
||
func (el *Element) Key(keys ...string) error {
|
||
if err := el.element.Focus(); err != nil {
|
||
return gojs.Err(err)
|
||
}
|
||
controlKeys := keyFilter(true, keys...)
|
||
// 按下所有控制键
|
||
err := keyAction(el.page, "press", controlKeys...)
|
||
if err == nil {
|
||
// 输入所有非控制键
|
||
err = keyAction(el.page, "type", keyFilter(false, keys...)...)
|
||
}
|
||
if err == nil {
|
||
// 释放所有控制键
|
||
err = keyAction(el.page, "release", controlKeys...)
|
||
}
|
||
return err
|
||
}
|
||
|
||
// IsVisible 判断元素是否可见
|
||
func (el *Element) IsVisible() (bool, error) {
|
||
visible, err := el.element.Visible()
|
||
if err != nil {
|
||
return false, gojs.Err(err)
|
||
}
|
||
return visible, nil
|
||
}
|
||
|
||
// GetPosition 获取元素位置(中心点)
|
||
func (el *Element) GetPosition() (*Position, error) {
|
||
shape, err := el.element.Shape()
|
||
if err != nil {
|
||
return nil, gojs.Err(err)
|
||
}
|
||
rect := shape.Box()
|
||
return &Position{
|
||
X: rect.X + rect.Width/2,
|
||
Y: rect.Y + rect.Height/2,
|
||
}, nil
|
||
}
|
||
|
||
// GetComputedStyle 获取元素所有计算样式
|
||
func (el *Element) GetComputedStyle() (map[string]string, error) {
|
||
r, err := el.element.Eval(`(el) => {
|
||
const styles = window.getComputedStyle(el);
|
||
const result = {};
|
||
for (let i = 0; i < styles.length; i++) {
|
||
const prop = styles[i];
|
||
result[prop] = styles.getPropertyValue(prop);
|
||
}
|
||
return result;
|
||
}`)
|
||
if err != nil {
|
||
return nil, gojs.Err(err)
|
||
}
|
||
|
||
style := r.Value.Map()
|
||
result := make(map[string]string)
|
||
for key, value := range style {
|
||
result[key] = value.String()
|
||
}
|
||
return result, nil
|
||
}
|