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.page) 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 } // Page级 func (pg *Page) Press(key string) error { return gojs.Err(pg.page.Keyboard.Press(input.Key(key[0]))) } 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) Press(key string) error { if err := el.element.Focus(); err != nil { return gojs.Err(err) } return gojs.Err(el.element.Page().Keyboard.Press(input.Key(key[0]))) } // 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 }