forked from go-rod/rod
-
Notifications
You must be signed in to change notification settings - Fork 0
Expand file tree
/
Copy pathexamples_test.go
More file actions
417 lines (322 loc) · 13.3 KB
/
Copy pathexamples_test.go
File metadata and controls
417 lines (322 loc) · 13.3 KB
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
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
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/go-rod/rod/lib/utils"
)
// 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().MustConnect()
// Even you forget to close, rod will close it after main process ends.
defer browser.MustClose()
// 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).MustPage("https://github.com")
// Make sure viewport is always consistent.
page.MustViewport(1200, 600, 1, false)
// We use css selector to get the search input element and input "git"
page.MustElement("input").MustInput("git").MustPress(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.MustElement(".codesearch-results p").MustText()
fmt.Println(text)
// Get all input elements. Rod supports query elements by css selector, xpath, and regex.
// For more detailed usage, check the query_test.go file.
fmt.Println(len(page.MustElements("input")))
// Eval js on the page
page.MustEval(`console.log("hello world")`)
// Pass parameters as json objects to the js function. This one will return 3
fmt.Println(page.MustEval(`(a, b) => a + b`, 1, 2).Int())
// When eval on an element, you can use "this" to access the DOM element.
fmt.Println(page.MustElement("title").MustEval(`this.innerText`).String())
// To handle errors in rod, you can use rod.Try or E suffixed function family like "page.ElementE"
// https://github.com/go-rod/rod#q-why-functions-dont-return-error-values
err := rod.Try(func() {
// Here we will catch timeout or query error
page.Timeout(time.Second / 2).MustElement("element-not-exists")
})
if errors.Is(err, context.DeadlineExceeded) {
fmt.Println("after 0.5 seconds, the element is still not rendered")
}
// Output:
// Git is the most widely used version control system.
// 5
// 3
// Search · git · GitHub
// after 0.5 seconds, the element is still not rendered
}
// Usage of timeout context
func Example_timeout() {
page := rod.New().MustConnect().MustPage("https://github.com")
page.
// Set a 5-second timeout for all chained actions
Timeout(5 * time.Second).
// The total time for MustWaitLoad and MustElement must be less than 5 seconds
MustWaitLoad().
MustElement("title").
// Actions after CancelTimeout won't be affected by the 5-second timeout
CancelTimeout().
// Set a 10-second timeout for all chained actions
Timeout(10 * time.Second).
// Panics if it takes more than 10 seconds
MustText()
}
// 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).MustConnect()
defer browser.MustClose()
page := browser.MustPage("https://developer.mozilla.org/en-US/docs/Web/HTML/Element/iframe")
// get the code mirror editor inside the iframe
el := page.MustSearch(".CodeMirror")
fmt.Println(*el.MustAttribute("class"))
// Output: CodeMirror cm-s-default CodeMirror-wrap
}
// Shows how we can start a browser with debug information and headless mode disabled to develop.
// Rod provides a lot of debug options, you can set them with setter methods or use environment variables.
// Doc for environment variables: https://pkg.go.dev/github.com/go-rod/rod/lib/defaults
func Example_disable_headless_to_debug() {
// Headless runs the browser on foreground, you can also use env "rod=show"
// Devtools opens the tab in each new tab opened automatically
l := launcher.New().
Headless(false).
Devtools(true)
defer l.Cleanup()
url := l.MustLaunch()
// 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).
MustConnect()
// ServeMonitor plays screenshots of each tab. This feature is extremely
// useful when debugging with headless mode.
browser.ServeMonitor(":9777", true)
defer browser.MustClose()
page := browser.MustPage("https://www.wikipedia.org/")
page.MustElement("#searchLanguage").MustSelect("[lang=zh]")
page.MustElement("#searchInput").MustInput("热干面")
page.Keyboard.MustPress(input.Enter)
fmt.Println(page.MustElement("#firstHeading").MustText())
// Response gets the binary of the image as a []byte.
img := page.MustElement(`[alt="Hot Dry Noodles.jpg"]`).MustResource()
fmt.Println(len(img)) // print the size of the image
utils.Pause() // pause goroutine
// 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).MustConnect()
defer browser.MustClose()
page := browser.MustPage("https://getbootstrap.com/docs/4.0/components/modal/")
page.MustWaitLoad().MustElement("[data-target='#exampleModalLive']").MustClick()
saveBtn := page.MustElementMatches("#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).MustWaitStable().MustClick().MustWaitInvisible()
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).MustConnect()
defer browser.MustClose()
page := browser.MustPage("https://duckduckgo.com/")
// WaitRequestIdle will wait for all possible ajax calls to complete before
// continuing on with further execution calls.
wait := page.MustWaitRequestIdle()
page.MustElement("#search_form_input_homepage").MustClick().MustInput("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.MustElements(".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).MustConnect()
defer browser.MustClose()
page := browser.MustPage("https://github.com")
// sleep for 0.5 seconds before every retry
sleeper := func() utils.Sleeper {
return func(context.Context) error {
time.Sleep(time.Second / 2)
return nil
}
}
el, _ := page.Sleeper(sleeper).Element("input")
// If sleeper is nil page.ElementE will query without retrying.
// If nothing found it will return an error.
el, err := page.Sleeper(nil).Element("input")
if errors.Is(err, rod.ErrElementNotFound) {
fmt.Println("element not found")
} else if err != nil {
panic(err)
}
fmt.Println(el.MustEval(`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").
MustLaunch()
browser := rod.New().ControlURL(url).MustConnect()
defer browser.MustClose()
// Adding authentication to the proxy, for the next auth request.
// We use CLI tool "mitmproxy --proxyauth user:pass" as an example.
browser.MustHandleAuth("user", "pass")
// mitmproxy needs a cert config to support https. We use http here instead,
// for example
fmt.Println(browser.MustPage("http://example.com/").MustElement("title").MustText())
// 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).MustConnect()
defer browser.MustClose()
// The code here shows how SetCookies works.
// Normally, you use something like
// browser.Page("").SetCookies(...).Navigate(url).
page := browser.MustPage("")
// 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)
if err != nil {
panic(err)
}
fmt.Println(res.Success)
page.MustNavigate("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.MustEval(`document.cookie`).String()
fmt.Println(cookie)
// You can also use your own raw JSON to send a json request.
params, _ := json.Marshal(map[string]string{
"name": "rod",
"value": "test",
"url": "https://example.com",
})
ctx, client, sessionID := page.CallContext()
_, _ = client.Call(ctx, sessionID, "Network.SetCookie", params)
// 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).MustConnect()
defer browser.MustClose()
page := browser.MustPage("")
done := make(chan int)
// Listen to all events of console output.
go page.EachEvent(func(e *proto.RuntimeConsoleAPICalled) {
log := page.MustObjectsToJSON(e.Args).Join(" ")
fmt.Println(log)
close(done)
})()
wait := page.WaitEvent(&proto.PageLoadEventFired{})
page.MustNavigate("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.MustNavigate("https://example.com")
wait()
}
page.MustEval(`console.log("hello", "world")`)
<-done
// Output:
// hello world
}
// Example_hijack_requests shows how we can intercept requests and modify
// both the request and the response.
// The entire process of hijacking one request:
// browser -req-> rod --> server --> rod -res-> browser
// The -req-> and -res-> are the parts that can be modified.
func Example_hijack_requests() {
browser := rod.New().Timeout(time.Minute).MustConnect()
defer browser.MustClose()
router := browser.HijackRequests()
defer router.MustStop()
router.MustAdd("*.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.Req().Header.Set("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.MustLoadResponse()
// Here we append some code to every js file.
// The code will update the document title to "hi"
ctx.Response.SetBody(ctx.Response.Body() + "\n document.title = 'hi' ")
})
go router.Run()
browser.MustPage("https://www.wikipedia.org/").MustWait(`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).MustConnect()
defer browser.MustClose()
page := browser.MustPage("")
// 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
}