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 获取元素名称(如) 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 }