Compare commits

...

4 Commits
v0.0.4 ... main

Author SHA1 Message Date
11f2454fca 修改启动Chrome时的配置 2025-07-24 19:23:38 +08:00
cc7573428b add Info for chrome 2025-07-24 19:13:07 +08:00
8b52007c65 支持配置远程的Chrome调试端口(例如docker)
更好的支持按键处理
2025-07-23 20:30:29 +08:00
93f1a05ef4 many fix 2025-07-22 20:45:03 +08:00
5 changed files with 452 additions and 64 deletions

115
chrome.go
View File

@ -8,12 +8,14 @@ import (
"apigo.cc/gojs/goja" "apigo.cc/gojs/goja"
"github.com/go-rod/rod" "github.com/go-rod/rod"
"github.com/go-rod/rod/lib/launcher" "github.com/go-rod/rod/lib/launcher"
"github.com/ssgo/log" "github.com/go-rod/rod/lib/launcher/flags"
"github.com/ssgo/u" "github.com/ssgo/u"
) )
type Chrome struct { type Chrome struct {
id string id string
chromeURL string
chromePath string
launcher *launcher.Launcher launcher *launcher.Launcher
browser *rod.Browser browser *rod.Browser
} }
@ -21,8 +23,11 @@ type Chrome struct {
var chromes = map[string]*Chrome{} var chromes = map[string]*Chrome{}
var chromesLock sync.Mutex var chromesLock sync.Mutex
func (ch *Chrome) Close() { func (ch *Chrome) Close(vm *goja.Runtime) {
// logger := gojs.GetLogger(vm)
if ch.browser != nil { if ch.browser != nil {
// ver, _ := ch.browser.Version()
// logger.Info("关闭Chrome浏览器", "id", ch.id, "browser", ver.Product, "userAgent", ver.UserAgent, "chromeURL", ch.chromeURL, "chromePath", ch.chromePath)
ch.browser.Close() ch.browser.Close()
ch.browser = nil ch.browser = nil
} }
@ -36,42 +41,118 @@ func (ch *Chrome) Close() {
chromesLock.Unlock() chromesLock.Unlock()
} }
func CloseAllChrome() { func CloseAllChrome(vm *goja.Runtime) {
n := len(chromes) n := len(chromes)
if n > 0 { if n > 0 {
for _, ch := range chromes { for _, ch := range chromes {
ch.Close() ch.Close(vm)
} }
log.DefaultLogger.Info("关闭所有 Chrome 浏览器", "count", n) logger := gojs.GetLogger(vm)
logger.Info("关闭所有未主动关闭的Chrome浏览器", "count", n)
} }
} }
func StartChrome(showWindow *bool, vm *goja.Runtime) (*Chrome, error) { type ChromeInfo struct {
logger := gojs.GetLogger(vm) ProtocolVersion string
Product string
Revision string
UserAgent string
JsVersion string
ChromeURL string
ChromePath string
}
type ChromeOption struct {
ChromeURL string
ChromeOption []string
ShowWindow bool
ChromeHtpProxy string
}
func (ch *Chrome) Info(showWindow *bool, vm *goja.Runtime) (ChromeInfo, error) {
ver, err := ch.browser.Version()
if err != nil {
return ChromeInfo{
ChromeURL: ch.chromeURL,
ChromePath: ch.chromePath,
}, err
}
return ChromeInfo{
ProtocolVersion: ver.ProtocolVersion,
Product: ver.Product,
Revision: ver.Revision,
UserAgent: ver.UserAgent,
JsVersion: ver.JsVersion,
ChromeURL: ch.chromeURL,
ChromePath: ch.chromePath,
}, nil
}
func StartChrome(opt *ChromeOption, vm *goja.Runtime) (*Chrome, error) {
if opt == nil {
opt = &ChromeOption{}
}
if opt.ChromeURL == "" {
opt.ChromeURL = conf.ChromeURL
}
if opt.ChromeHtpProxy == "" {
opt.ChromeHtpProxy = conf.ChromeHtpProxy
}
if opt.ChromeOption == nil {
opt.ChromeOption = conf.ChromeOption
}
// logger := gojs.GetLogger(vm)
ch := &Chrome{} ch := &Chrome{}
ch.id = u.UniqueId()
ch.browser = rod.New() ch.browser = rod.New()
ch.chromeURL = opt.ChromeURL
if opt.ChromeURL == "" {
// 使用本地Chrome
ch.launcher = launcher.New()
if localBrowserPath, hasLocalBrowser := launcher.LookPath(); hasLocalBrowser { if localBrowserPath, hasLocalBrowser := launcher.LookPath(); hasLocalBrowser {
ch.launcher = launcher.New().Bin(localBrowserPath).Headless(!u.Bool(showWindow)).Set("no-sandbox").Set("disable-gpu").Set("disable-dev-shm-usage").Set("single-process") ch.launcher.Bin(localBrowserPath)
ch.chromePath = localBrowserPath
}
// "--headless=new", "--no-sandbox", "--disable-dev-shm-usage", "--hide-scrollbars", "--font-render-hinting=none", "--disable-blink-features=AutomationControlled", "--disable-infobars", "--lang=zh-CN,zh", "--disable-extensions", "--disable-gpu", "--use-gl=swiftshader", "--ignore-gpu-blocklist", "--use-angle=swiftshader", "--disable-features=Translate"
ch.launcher.Headless(!opt.ShowWindow).Set("disable-dev-shm-usage").Set("single-process").Set("disable-blink-features", "AutomationControlled")
ch.launcher.Set("user-agent", "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/125.0.0.0 Safari/537.36 Edg/125.0.0.0")
if opt.ChromeHtpProxy != "" {
ch.launcher.Proxy(opt.ChromeHtpProxy)
}
switch runtime.GOOS { switch runtime.GOOS {
case "linux": case "linux":
ch.launcher.Set("disable-setuid-sandbox") ch.launcher.Set("disable-setuid-sandbox")
case "windows": case "windows":
ch.launcher.Set("disable-features=RendererCodeIntegrity") ch.launcher.Set("disable-features=RendererCodeIntegrity")
} }
localChromeURL, err := ch.launcher.Launch() if opt.ChromeOption != nil {
if err != nil { for _, opt := range opt.ChromeOption {
ch.Close() a := u.SplitTrimN(opt, "=", 2)
return nil, gojs.Err(err) if len(a) == 2 {
ch.launcher.Set(flags.Flag(a[0]), a[1])
} else {
ch.launcher.Set(flags.Flag(opt))
} }
logger.Info("启动本地 Chrome 浏览器", "url", localChromeURL, "path", localBrowserPath)
ch.browser.ControlURL(localChromeURL)
} }
if err := ch.browser.Connect(); err != nil { }
ch.Close() if localChromeURL, err := ch.launcher.Launch(); err != nil {
ch.Close(vm)
return nil, gojs.Err(err) return nil, gojs.Err(err)
} else {
ch.chromeURL = localChromeURL
if ch.chromePath == "" {
ch.chromePath = ch.launcher.Get(flags.Bin)
}
}
} }
ch.id = u.UniqueId() ch.browser.ControlURL(ch.chromeURL)
if err := ch.browser.Connect(); err != nil {
return nil, gojs.Err(err)
}
// ver, _ := ch.browser.Version()
// logger.Info("启动Chrome浏览器", "id", ch.id, "browser", ver.Product, "userAgent", ver.UserAgent, "chromeURL", ch.chromeURL, "chromePath", ch.chromePath)
chromesLock.Lock() chromesLock.Lock()
chromes[ch.id] = ch chromes[ch.id] = ch
chromesLock.Unlock() chromesLock.Unlock()

8
go.mod
View File

@ -3,15 +3,15 @@ module apigo.cc/gojs/http
go 1.23.0 go 1.23.0
require ( require (
apigo.cc/gojs v0.0.21 apigo.cc/gojs v0.0.23
apigo.cc/gojs/console v0.0.2 apigo.cc/gojs/console v0.0.2
apigo.cc/gojs/file v0.0.4 apigo.cc/gojs/file v0.0.4
apigo.cc/gojs/util v0.0.12 apigo.cc/gojs/util v0.0.12
github.com/go-rod/rod v0.112.8 github.com/go-rod/rod v0.116.2
github.com/gorilla/websocket v1.5.3 github.com/gorilla/websocket v1.5.3
github.com/ssgo/config v1.7.9 github.com/ssgo/config v1.7.9
github.com/ssgo/httpclient v1.7.8 github.com/ssgo/httpclient v1.7.8
github.com/ssgo/log v1.7.7 github.com/ssgo/log v1.7.9
github.com/ssgo/s v1.7.24 github.com/ssgo/s v1.7.24
github.com/ssgo/u v1.7.21 github.com/ssgo/u v1.7.21
) )
@ -36,7 +36,9 @@ require (
github.com/ssgo/tool v0.4.29 // indirect github.com/ssgo/tool v0.4.29 // indirect
github.com/tklauser/go-sysconf v0.3.15 // indirect github.com/tklauser/go-sysconf v0.3.15 // indirect
github.com/tklauser/numcpus v0.10.0 // indirect github.com/tklauser/numcpus v0.10.0 // indirect
github.com/ysmood/fetchup v0.2.3 // indirect
github.com/ysmood/goob v0.4.0 // indirect github.com/ysmood/goob v0.4.0 // indirect
github.com/ysmood/got v0.40.0 // indirect
github.com/ysmood/gson v0.7.3 // indirect github.com/ysmood/gson v0.7.3 // indirect
github.com/ysmood/leakless v0.9.0 // indirect github.com/ysmood/leakless v0.9.0 // indirect
github.com/yusufpapurcu/wmi v1.2.4 // indirect github.com/yusufpapurcu/wmi v1.2.4 // indirect

View File

@ -34,7 +34,9 @@ var defaultHttp = &Http{
var conf = struct { var conf = struct {
Timeout int Timeout int
ChromePath string ChromeURL string
ChromeOption []string
ChromeHtpProxy string
}{} }{}
func init() { func init() {
@ -78,7 +80,9 @@ func init() {
}, },
Desc: "", Desc: "",
Example: "", Example: "",
WaitForStop: CloseAllChrome, WaitForStop: func() {
CloseAllChrome(nil)
},
}) })
} }

View File

@ -26,9 +26,6 @@ function upload(url: string, form: Object, files: Object, headers?: Object): Res
function download(filename: string, url: string, callback?: (finished: number, total: number) => void, headers?: Object): Result { return null as any } function download(filename: string, url: string, callback?: (finished: number, total: number) => void, headers?: Object): Result { return null as any }
function connect(url: string, config?: WSConfig): WS { return null as any } function connect(url: string, config?: WSConfig): WS { return null as any }
interface Chrome {
}
interface Client { interface Client {
get(url: string, headers?: Object): Result get(url: string, headers?: Object): Result
head(url: string, headers?: Object): Result head(url: string, headers?: Object): Result

364
page.go
View File

@ -17,6 +17,7 @@ import (
type Page struct { type Page struct {
page *rod.Page page *rod.Page
longTimeoutPage *rod.Page longTimeoutPage *rod.Page
originPage *rod.Page
// Res []Resource // Res []Resource
} }
@ -47,24 +48,9 @@ func (ch *Chrome) Open(url string) (*Page, error) {
return nil, gojs.Err(err) return nil, gojs.Err(err)
} }
longTimeoutPage := page.Timeout(time.Second * 15) pg := &Page{originPage: page}
page = page.Timeout(time.Second * 5) pg.ResetTimeout()
pg.WaitPageLoad()
pg := &Page{page: page, longTimeoutPage: longTimeoutPage}
// pg := &Page{page: page, longTimeoutPage: longTimeoutPage, Res: []Resource{}}
// proto.NetworkEnable{}.Call(page)
// longTimeoutPage.EachEvent(func(e *proto.NetworkResponseReceived) {
// fmt.Println(u.BMagenta(e.Response.URL), e.Type, e.Response.Status, e.Response.Headers["Content-Type"].Str(), e.Response.Headers["Content-Length"].Int())
// pg.Res = append(pg.Res, Resource{
// URL: e.Response.URL,
// Type: string(e.Type),
// Status: e.Response.Status,
// Size: e.Response.Headers["Content-Length"].Int(),
// MimeType: e.Response.Headers["Content-Type"].Str(),
// })
// })
pg.longTimeoutPage.WaitLoad()
return pg, nil return pg, nil
} }
@ -74,7 +60,7 @@ func (pg *Page) Close() error {
} }
func (pg *Page) GetResList() ([]Resource, error) { func (pg *Page) GetResList() ([]Resource, error) {
r, err := proto.PageGetResourceTree{}.Call(pg.page) r, err := proto.PageGetResourceTree{}.Call(pg.longTimeoutPage)
if err != nil { if err != nil {
return nil, gojs.Err(err) return nil, gojs.Err(err)
} }
@ -198,13 +184,19 @@ func (pg *Page) Navigate(url string) error {
if err != nil { if err != nil {
return gojs.Err(err) return gojs.Err(err)
} }
pg.longTimeoutPage.WaitLoad() pg.WaitPageLoad()
return nil return nil
} }
// WaitLoad 等待页面加载完成 // WaitLoad 等待页面加载完成
func (pg *Page) WaitPageLoad() error { func (pg *Page) WaitPageLoad() error {
return gojs.Err(pg.longTimeoutPage.WaitLoad()) time.Sleep(100 * time.Millisecond)
err := pg.page.WaitLoad()
if err != nil {
return gojs.Err(err)
}
pg.ResetTimeout()
return nil
} }
// WaitIdle 等待页面空闲无网络请求和JS执行 // WaitIdle 等待页面空闲无网络请求和JS执行
@ -215,6 +207,28 @@ func (pg *Page) WaitIdle(ms *int) error {
return gojs.Err(pg.page.WaitIdle(time.Millisecond * time.Duration(*ms))) 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稳定 // WaitStable 等待页面DOM稳定
// func (pg *Page) WaitStable(ms *int) error { // func (pg *Page) WaitStable(ms *int) error {
// if ms == nil { // if ms == nil {
@ -367,9 +381,267 @@ func (pg *Page) Frame(selector string) (*Page, error) {
return &Page{page: p}, nil return &Page{page: p}, nil
} }
// Page级 var isControlKey = map[string]bool{
func (pg *Page) Press(key string) error { "ShiftLeft": true,
return gojs.Err(pg.page.Keyboard.Press(input.Key(key[0]))) "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 { func (pg *Page) SetUserAgent(ua string) error {
@ -391,7 +663,7 @@ func (pg *Page) Back() error {
if err != nil { if err != nil {
return gojs.Err(err) return gojs.Err(err)
} }
pg.longTimeoutPage.WaitIdle(time.Second) pg.WaitPageLoad()
return nil return nil
} }
@ -400,7 +672,7 @@ func (pg *Page) Forward() error {
if err != nil { if err != nil {
return gojs.Err(err) return gojs.Err(err)
} }
pg.longTimeoutPage.WaitIdle(time.Second) pg.WaitPageLoad()
return nil return nil
} }
@ -447,7 +719,7 @@ func (el *Element) Text() (string, error) {
} }
func (el *Element) InnerHTML() (string, error) { func (el *Element) InnerHTML() (string, error) {
r, err := el.element.Eval(`el => el.innerHTML`) r, err := el.element.Eval(`el => {return el.innerHTML}`)
if err != nil { if err != nil {
return "", gojs.Err(err) return "", gojs.Err(err)
} }
@ -1061,7 +1333,7 @@ func (el *Element) RemoveProperty(name string) error {
// IsInteractable 元素是否可交互 // IsInteractable 元素是否可交互
func (el *Element) IsInteractable() (bool, error) { func (el *Element) IsInteractable() (bool, error) {
_, err := el.element.Interactable() _, err := el.element.Interactable()
if errors.Is(err, &rod.ErrNotInteractable{}) { if errors.Is(err, &rod.NotInteractableError{}) {
return false, nil return false, nil
} }
if err != nil { if err != nil {
@ -1078,11 +1350,43 @@ func (el *Element) WaitStable(ms *int) error {
} }
// 元素级 // 元素级
func (el *Element) Press(key string) error { func (el *Element) KeyPress(keys ...string) error {
if err := el.element.Focus(); err != nil { if err := el.element.Focus(); err != nil {
return gojs.Err(err) return gojs.Err(err)
} }
return gojs.Err(el.element.Page().Keyboard.Press(input.Key(key[0]))) 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 判断元素是否可见 // IsVisible 判断元素是否可见