-
-
Notifications
You must be signed in to change notification settings - Fork 354
/
examples_test.go
388 lines (301 loc) · 12.4 KB
/
examples_test.go
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
package rod_test
import (
"context"
"encoding/json"
"errors"
"fmt"
"time"
"github.com/go-rod/rod"
"github.com/go-rod/rod/lib/input"
"github.com/go-rod/rod/lib/launcher"
"github.com/go-rod/rod/lib/proto"
"github.com/ysmood/kit"
)
// Example_basic is a simple test that opens https://github.com/, searches for
// "git", and then gets the header element which gives the description for Git.
func Example_basic() {
// Launch a new browser with default options, and connect to it.
browser := rod.New().Connect()
// Even you forget to close, rod will close it after main process ends.
defer browser.Close()
// Timeout will be passed to all chained function calls.
// The code will panic out if any chained call is used after the timeout.
page := browser.Timeout(time.Minute).Page("https://github.com")
// Resize the window make sure window size is always consistent.
page.Window(0, 0, 1200, 600)
// We use css selector to get the search input element and input "git"
page.Element("input").Input("git").Press(input.Enter)
// Wait until css selector get the element then get the text content of it.
// You can also pass multiple selectors to race the result, useful when dealing with multiple possible results.
text := page.Element(".codesearch-results p").Text()
fmt.Println(text)
// Eval js on the page
page.Eval(`console.log("hello world")`)
// Pass parameters as json objects to the js function. This one will return 3
fmt.Println(page.Eval(`(a, b) => a + b`, 1, 2).Int())
// When eval on an element, you can use "this" to access the DOM element.
fmt.Println(page.Element("title").Eval(`this.innerText`).String())
// To handle errors in rod, you can use rod.Try or E suffixed function family like "page.ElementE"
err := rod.Try(func() {
page.Element("#2020")
})
if errors.Is(err, rod.ErrEval) {
// https://stackoverflow.com/questions/20306204/using-queryselector-with-ids-that-are-numbers
fmt.Println("you need to learn how to use css selector")
}
// Output:
// Git is the most widely used version control system.
// 3
// Search · git · GitHub
// you need to learn how to use css selector
}
// Example_search shows how to use Search to get element inside nested iframes or shadow DOMs.
// It works the same as https://developers.google.com/web/tools/chrome-devtools/dom#search
func Example_search() {
browser := rod.New().Timeout(time.Minute).Connect()
defer browser.Close()
page := browser.Page("https://developer.mozilla.org/en-US/docs/Web/HTML/Element/iframe")
// get the code mirror editor inside the iframe
el := page.Search(".CodeMirror")
fmt.Println(*el.Attribute("class"))
// Output: CodeMirror cm-s-default CodeMirror-wrap
}
// Example_headless_with_debug shows how we can start a browser with debug
// information and headless mode disabled to show the browser in the foreground.
// Rod provides a lot of debug options, you can use the Set method to enable
// them or use environment variables. (Default environment variables can be
// found in "lib/defaults").
func Example_headless_with_debug() {
// Headless runs the browser on foreground, you can also use env "rod=show"
// Devtools opens the tab in each new tab opened automatically
url := launcher.New().
Headless(false).
Devtools(true).
Launch()
// Trace shows verbose debug information for each action executed
// Slowmotion is a debug related function that waits 2 seconds between
// each action, making it easier to inspect what your code is doing.
browser := rod.New().
Timeout(time.Minute).
ControlURL(url).
Trace(true).
Slowmotion(2 * time.Second).
Connect()
// ServeMonitor plays screenshots of each tab. This feature is extremely
// useful when debugging with headless mode.
browser.ServeMonitor(":9777", true)
defer browser.Close()
page := browser.Page("https://www.wikipedia.org/")
page.Element("#searchLanguage").Select("[lang=zh]")
page.Element("#searchInput").Input("热干面")
page.Keyboard.Press(input.Enter)
fmt.Println(page.Element("#firstHeading").Text())
// Response gets the binary of the image as a []byte.
// We use OutputFile to write the content of the image into ./tmp/img.jpg
img := page.Element(`[alt="Hot Dry Noodles.jpg"]`)
_ = kit.OutputFile("tmp/img.jpg", img.Resource(), nil)
// Pause temporarily halts JavaScript execution on the website.
// You can resume execution in the devtools window by clicking the resume
// button in the "source" tab.
page.Pause()
// Skip
// Output: 热干面
}
// Example_wait_for_animation is an example to simulate humans more accurately.
// If a button is moving too fast, you cannot click it as a human. To more
// accurately simulate human inputs, actions triggered by Rod can be based on
// mouse point location, so you can allow Rod to wait for the button to become
// stable before it tries clicking it.
func Example_wait_for_animation() {
browser := rod.New().Timeout(time.Minute).Connect()
defer browser.Close()
page := browser.Page("https://getbootstrap.com/docs/4.0/components/modal/")
page.WaitLoad().Element("[data-target='#exampleModalLive']").Click()
saveBtn := page.ElementMatches("#exampleModalLive button", "Close")
// Here, WaitStable will wait until the save button's position becomes
// stable. The timeout is 5 seconds, after which it times out (or after 1
// minute since the browser was created). Timeouts from parents are
// inherited to the children as well.
saveBtn.Timeout(5 * time.Second).WaitStable().Click().WaitInvisible()
fmt.Println("done")
// Output: done
}
// Example_wait_for_request shows an example where Rod will wait for all
// requests on the page to complete (such as network request) before interacting
// with the page.
func Example_wait_for_request() {
browser := rod.New().Timeout(time.Minute).Connect()
defer browser.Close()
page := browser.Page("https://duckduckgo.com/")
// WaitRequestIdle will wait for all possible ajax calls to complete before
// continuing on with further execution calls.
wait := page.WaitRequestIdle()
page.Element("#search_form_input_homepage").Click().Input("test")
time.Sleep(300 * time.Millisecond) // Wait for js debounce.
wait()
// We want to make sure that after waiting, there are some autocomplete
// suggestions available.
fmt.Println(len(page.Elements(".search__autocomplete .acp")) > 0)
// Output: true
}
// Example_customize_retry_strategy allows us to change the retry/polling
// options that is used to query elements. This is useful when you want to
// customize the element query retry logic.
func Example_customize_retry_strategy() {
browser := rod.New().Timeout(time.Minute).Connect()
defer browser.Close()
page := browser.Page("https://github.com")
backoff := kit.BackoffSleeper(30*time.Millisecond, 3*time.Second, nil)
// ElementE is used in this context instead of Element. When The XxxxxE
// version of functions are used, there will be more access to customise
// options, like give access to the backoff algorithm.
el, err := page.Timeout(10*time.Second).ElementE(backoff, "", []string{"input"})
if err == context.DeadlineExceeded {
fmt.Println("unable to find the input element before timeout")
} else {
kit.E(err)
}
// ElementE with the Sleeper parameter being nil will get the element
// without retrying. Instead returning an error.
el, err = page.ElementE(nil, "", []string{"input"})
if errors.Is(err, rod.ErrElementNotFound) {
fmt.Println("element not found")
} else {
kit.E(err)
}
fmt.Println(el.Eval(`this.name`).String())
// Output: q
}
// Example_customize_browser_launch will show how we can further customise the
// browser with the launcher library. The launcher lib comes with many default
// flags (switches), this example adds and removes a few.
func Example_customize_browser_launch() {
// Documentation for default switches can be found at the source of the
// launcher.New function, as well as at
// https://peter.sh/experiments/chromium-command-line-switches/.
url := launcher.New().
// Set a flag- Adding the HTTP proxy server.
Set("proxy-server", "127.0.0.1:8080").
// Delete a flag- remove the mock-keychain flag
Delete("use-mock-keychain").
Launch()
browser := rod.New().ControlURL(url).Connect()
defer browser.Close()
// Adding authentication to the proxy, for the next auth request.
// We use CLI tool "mitmproxy --proxyauth user:pass" as an example.
browser.HandleAuth("user", "pass")
// mitmproxy needs a cert config to support https. We use http here instead,
// for example
fmt.Println(browser.Page("http://example.com/").Element("title").Text())
// Skip
// Output: Example Domain
}
// Example_direct_cdp shows how we can use Rod when it doesn't have a function
// or a feature that you would like to use. You can easily call the cdp
// interface.
func Example_direct_cdp() {
browser := rod.New().Timeout(time.Minute).Connect()
defer browser.Close()
// The code here shows how SetCookies works.
// Normally, you use something like
// browser.Page("").SetCookies(...).Navigate(url).
page := browser.Page("")
// Call the cdp interface directly.
// We set the cookie before we visit the website.
// The "proto" lib contains every JSON schema you may need to communicate
// with browser
res, err := proto.NetworkSetCookie{
Name: "rod",
Value: "test",
URL: "https://example.com",
}.Call(page)
kit.E(err)
fmt.Println(res.Success)
page.Navigate("https://example.com")
// Eval injects a script into the page. We use this to return the cookies
// that JS detects to validate our cdp call.
cookie := page.Eval(`document.cookie`).String()
fmt.Println(cookie)
// You can also use your own raw JSON to send a json request.
data, _ := json.Marshal(map[string]string{
"name": "rod",
"value": "test",
"url": "https://example.com",
})
_, _ = browser.Call(page.GetContext(), string(page.SessionID), "Network.SetCookie", data)
// Output:
// true
// rod=test
}
// Example_handle_events is an example showing how we can use Rod to subscribe
// to events.
func Example_handle_events() {
browser := rod.New().Timeout(time.Minute).Connect()
defer browser.Close()
page := browser.Page("")
done := make(chan kit.Nil)
// Listen to all events of console output.
go page.EachEvent(func(e *proto.RuntimeConsoleAPICalled) {
log := page.ObjectsToJSON(e.Args).Join(" ")
fmt.Println(log)
close(done)
})()
wait := page.WaitEvent(&proto.PageLoadEventFired{})
page.Navigate("https://example.com")
wait()
// EachEvent allows us to achieve the same functionality as above.
if false {
// Subscribe events before they happen, run the "wait()" to start consuming
// the events. We can return an optional stop signal unsubscribe events.
wait := page.EachEvent(func(e *proto.PageLoadEventFired) (stop bool) {
return true
})
page.Navigate("https://example.com")
wait()
}
page.Eval(`console.log("hello", "world")`)
<-done
// Output:
// hello world
}
// Example_hijack_requests shows how we can intercept requests and modify
// both the request or the response.
func Example_hijack_requests() {
browser := rod.New().Timeout(time.Minute).Connect()
defer browser.Close()
router := browser.HijackRequests()
defer router.Stop()
router.Add("*.js", func(ctx *rod.Hijack) {
// Here we update the request's header. Rod gives functionality to
// change or update all parts of the request. Refer to the documentation
// for more information.
ctx.Request.SetHeader("My-Header", "test")
// LoadResponse runs the default request to the destination of the request.
// Not calling this will require you to mock the entire response.
// This can be done with the SetXxx (Status, Header, Body) functions on the
// response struct.
ctx.LoadResponse()
// Here we update the body of all requests to update the document title to "hi"
ctx.Response.SetBody(ctx.Response.StringBody() + "\n document.title = 'hi' ")
})
go router.Run()
browser.Page("https://www.wikipedia.org/").Wait(`document.title === 'hi'`)
fmt.Println("done")
// Output: done
}
// Example_states allows us to update the state of the current page.
// In this example we enable network access.
func Example_states() {
browser := rod.New().Timeout(time.Minute).Connect()
defer browser.Close()
page := browser.Page("")
// LoadState detects whether the network is enabled or not.
fmt.Println(page.LoadState(&proto.NetworkEnable{}))
_ = proto.NetworkEnable{}.Call(page)
// Now that we called the request on the page, we check see if the state
// result updates to true.
fmt.Println(page.LoadState(&proto.NetworkEnable{}))
// Output:
// false
// true
}