Book a Demo!
CoCalc Logo Icon
StoreFeaturesDocsShareSupportNewsAboutPoliciesSign UpSign In
projectdiscovery
GitHub Repository: projectdiscovery/nuclei
Path: blob/dev/pkg/protocols/headless/engine/page_actions.go
2072 views
1
package engine
2
3
import (
4
"context"
5
"fmt"
6
"os"
7
"path/filepath"
8
"reflect"
9
"strconv"
10
"strings"
11
"sync"
12
"time"
13
14
"github.com/go-rod/rod"
15
"github.com/go-rod/rod/lib/input"
16
"github.com/go-rod/rod/lib/proto"
17
"github.com/go-rod/rod/lib/utils"
18
"github.com/kitabisa/go-ci"
19
"github.com/pkg/errors"
20
"github.com/projectdiscovery/gologger"
21
"github.com/projectdiscovery/nuclei/v3/pkg/protocols/common/contextargs"
22
"github.com/projectdiscovery/nuclei/v3/pkg/protocols/common/expressions"
23
"github.com/projectdiscovery/nuclei/v3/pkg/protocols/common/protocolstate"
24
contextutil "github.com/projectdiscovery/utils/context"
25
"github.com/projectdiscovery/utils/errkit"
26
fileutil "github.com/projectdiscovery/utils/file"
27
folderutil "github.com/projectdiscovery/utils/folder"
28
stringsutil "github.com/projectdiscovery/utils/strings"
29
urlutil "github.com/projectdiscovery/utils/url"
30
"github.com/segmentio/ksuid"
31
)
32
33
var (
34
errinvalidArguments = errkit.New("invalid arguments provided")
35
ErrLFAccessDenied = errkit.New("Use -allow-local-file-access flag to enable local file access")
36
// ErrActionExecDealine is the error returned when alloted time for action execution exceeds
37
ErrActionExecDealine = errkit.New("headless action execution deadline exceeded").SetKind(errkit.ErrKindDeadline).Build()
38
)
39
40
const (
41
errCouldNotGetElement = "could not get element"
42
errCouldNotScroll = "could not scroll into view"
43
errElementDidNotAppear = "Element did not appear in the given amount of time"
44
)
45
46
// ExecuteActions executes a list of actions on a page.
47
func (p *Page) ExecuteActions(input *contextargs.Context, actions []*Action) (outData ActionData, err error) {
48
outData = make(ActionData)
49
50
// waitFuncs are function that needs to be executed after navigation
51
// typically used for waitEvent
52
waitFuncs := make([]func() error, 0)
53
54
// avoid any future panics caused due to go-rod library
55
// TODO(dwisiswant0): remove this once we get the RCA.
56
defer func() {
57
if ci.IsCI() {
58
return
59
}
60
61
if r := recover(); r != nil {
62
err = errkit.Newf("panic on headless action: %v", r)
63
}
64
}()
65
66
for _, act := range actions {
67
switch act.ActionType.ActionType {
68
case ActionNavigate:
69
err = p.NavigateURL(act, outData)
70
if err == nil {
71
// if navigation successful trigger all waitFuncs (if any)
72
for _, waitFunc := range waitFuncs {
73
if waitFunc != nil {
74
if err := waitFunc(); err != nil {
75
return nil, errkit.Wrap(err, "error occurred while executing waitFunc")
76
}
77
}
78
}
79
80
p.lastActionNavigate = act
81
}
82
case ActionScript:
83
err = p.RunScript(act, outData)
84
case ActionClick:
85
err = p.ClickElement(act, outData)
86
case ActionRightClick:
87
err = p.RightClickElement(act, outData)
88
case ActionTextInput:
89
err = p.InputElement(act, outData)
90
case ActionScreenshot:
91
err = p.Screenshot(act, outData)
92
case ActionTimeInput:
93
err = p.TimeInputElement(act, outData)
94
case ActionSelectInput:
95
err = p.SelectInputElement(act, outData)
96
case ActionWaitDOM:
97
event := proto.PageLifecycleEventNameDOMContentLoaded
98
err = p.WaitPageLifecycleEvent(act, outData, event)
99
case ActionWaitFCP:
100
event := proto.PageLifecycleEventNameFirstContentfulPaint
101
err = p.WaitPageLifecycleEvent(act, outData, event)
102
case ActionWaitFMP:
103
event := proto.PageLifecycleEventNameFirstMeaningfulPaint
104
err = p.WaitPageLifecycleEvent(act, outData, event)
105
case ActionWaitIdle:
106
event := proto.PageLifecycleEventNameNetworkIdle
107
err = p.WaitPageLifecycleEvent(act, outData, event)
108
case ActionWaitLoad:
109
event := proto.PageLifecycleEventNameLoad
110
err = p.WaitPageLifecycleEvent(act, outData, event)
111
case ActionWaitStable:
112
err = p.WaitStable(act, outData)
113
// NOTE(dwisiswant0): Mapping `ActionWaitLoad` to `Page.WaitStable`,
114
// just in case waiting for the `proto.PageLifecycleEventNameLoad` event
115
// doesn't meet expectations.
116
// case ActionWaitLoad, ActionWaitStable:
117
// err = p.WaitStable(act, outData)
118
case ActionGetResource:
119
err = p.GetResource(act, outData)
120
case ActionExtract:
121
err = p.ExtractElement(act, outData)
122
case ActionWaitEvent:
123
var waitFunc func() error
124
waitFunc, err = p.WaitEvent(act, outData)
125
if waitFunc != nil {
126
waitFuncs = append(waitFuncs, waitFunc)
127
}
128
case ActionWaitDialog:
129
err = p.HandleDialog(act, outData)
130
case ActionFilesInput:
131
if p.options.Options.AllowLocalFileAccess {
132
err = p.FilesInput(act, outData)
133
} else {
134
err = ErrLFAccessDenied
135
}
136
case ActionAddHeader:
137
err = p.ActionAddHeader(act, outData)
138
case ActionSetHeader:
139
err = p.ActionSetHeader(act, outData)
140
case ActionDeleteHeader:
141
err = p.ActionDeleteHeader(act, outData)
142
case ActionSetBody:
143
err = p.ActionSetBody(act, outData)
144
case ActionSetMethod:
145
err = p.ActionSetMethod(act, outData)
146
case ActionKeyboard:
147
err = p.KeyboardAction(act, outData)
148
case ActionDebug:
149
err = p.DebugAction(act, outData)
150
case ActionSleep:
151
err = p.SleepAction(act, outData)
152
case ActionWaitVisible:
153
err = p.WaitVisible(act, outData)
154
default:
155
continue
156
}
157
if err != nil {
158
return nil, errors.Wrap(err, "error occurred executing action")
159
}
160
}
161
return outData, nil
162
}
163
164
type rule struct {
165
*sync.Once
166
Action ActionType
167
Part string
168
Args map[string]string
169
}
170
171
// WaitVisible waits until an element appears.
172
func (p *Page) WaitVisible(act *Action, out ActionData) error {
173
timeout, err := getTimeout(p, act)
174
if err != nil {
175
return errors.Wrap(err, "Wrong timeout given")
176
}
177
178
pollTime, err := getTimeParameter(p, act, "pollTime", 100, time.Millisecond)
179
if err != nil {
180
return errors.Wrap(err, "Wrong polling time given")
181
}
182
183
element, _ := p.Sleeper(pollTime, timeout).
184
Timeout(timeout).
185
pageElementBy(act.Data)
186
187
if element != nil {
188
if err := element.WaitVisible(); err != nil {
189
return errors.Wrap(err, errElementDidNotAppear)
190
}
191
} else {
192
return errors.New(errElementDidNotAppear)
193
}
194
195
return nil
196
}
197
198
func (p *Page) Sleeper(pollTimeout, timeout time.Duration) *Page {
199
page := *p
200
page.page = page.Page().Sleeper(func() utils.Sleeper {
201
return createBackOffSleeper(pollTimeout, timeout)
202
})
203
return &page
204
}
205
206
func (p *Page) Timeout(timeout time.Duration) *Page {
207
page := *p
208
page.page = page.Page().Timeout(timeout)
209
return &page
210
}
211
212
func createBackOffSleeper(pollTimeout, timeout time.Duration) utils.Sleeper {
213
backoffSleeper := utils.BackoffSleeper(pollTimeout, timeout, func(duration time.Duration) time.Duration {
214
return duration
215
})
216
217
return func(ctx context.Context) error {
218
if ctx.Err() != nil {
219
return ctx.Err()
220
}
221
222
return backoffSleeper(ctx)
223
}
224
}
225
226
func getNavigationFunc(p *Page, act *Action, event proto.PageLifecycleEventName) (func(), error) {
227
dur, err := getTimeout(p, act)
228
if err != nil {
229
return nil, errors.Wrap(err, "Wrong timeout given")
230
}
231
232
fn := p.page.Timeout(dur).WaitNavigation(event)
233
234
return fn, nil
235
}
236
237
func getTimeout(p *Page, act *Action) (time.Duration, error) {
238
return getTimeParameter(p, act, "timeout", 5, time.Second)
239
}
240
241
// getTimeParameter returns a time parameter from an action. It first tries to
242
// get the parameter as an integer, then as a time.Duration, and finally falls
243
// back to the default value (multiplied by the unit).
244
func getTimeParameter(p *Page, act *Action, argName string, defaultValue, unit time.Duration) (time.Duration, error) {
245
argValue, err := p.getActionArg(act, argName)
246
if err != nil {
247
return time.Duration(0), err
248
}
249
250
convertedValue, err := strconv.Atoi(argValue)
251
if err == nil {
252
return time.Duration(convertedValue) * unit, nil
253
}
254
255
// fallback to time.ParseDuration
256
parsedTimeValue, err := time.ParseDuration(argValue)
257
if err == nil {
258
return parsedTimeValue, nil
259
}
260
261
return defaultValue * unit, nil
262
}
263
264
// ActionAddHeader executes a AddHeader action.
265
func (p *Page) ActionAddHeader(act *Action, out ActionData) error {
266
args := make(map[string]string)
267
268
part, err := p.getActionArg(act, "part")
269
if err != nil {
270
return err
271
}
272
273
args["key"], err = p.getActionArg(act, "key")
274
if err != nil {
275
return err
276
}
277
278
args["value"], err = p.getActionArg(act, "value")
279
if err != nil {
280
return err
281
}
282
283
p.rules = append(p.rules, rule{
284
Action: ActionAddHeader,
285
Part: part,
286
Args: args,
287
})
288
289
return nil
290
}
291
292
// ActionSetHeader executes a SetHeader action.
293
func (p *Page) ActionSetHeader(act *Action, out ActionData) error {
294
args := make(map[string]string)
295
296
part, err := p.getActionArg(act, "part")
297
if err != nil {
298
return err
299
}
300
301
args["key"], err = p.getActionArg(act, "key")
302
if err != nil {
303
return err
304
}
305
306
args["value"], err = p.getActionArg(act, "value")
307
if err != nil {
308
return err
309
}
310
311
p.rules = append(p.rules, rule{
312
Action: ActionSetHeader,
313
Part: part,
314
Args: args,
315
})
316
317
return nil
318
}
319
320
// ActionDeleteHeader executes a DeleteHeader action.
321
func (p *Page) ActionDeleteHeader(act *Action, out ActionData) error {
322
args := make(map[string]string)
323
324
part, err := p.getActionArg(act, "part")
325
if err != nil {
326
return err
327
}
328
329
args["key"], err = p.getActionArg(act, "key")
330
if err != nil {
331
return err
332
}
333
334
p.rules = append(p.rules, rule{
335
Action: ActionDeleteHeader,
336
Part: part,
337
Args: args,
338
})
339
340
return nil
341
}
342
343
// ActionSetBody executes a SetBody action.
344
func (p *Page) ActionSetBody(act *Action, out ActionData) error {
345
args := make(map[string]string)
346
347
part, err := p.getActionArg(act, "part")
348
if err != nil {
349
return err
350
}
351
352
args["body"], err = p.getActionArg(act, "body")
353
if err != nil {
354
return err
355
}
356
357
p.rules = append(p.rules, rule{
358
Action: ActionSetBody,
359
Part: part,
360
Args: args,
361
})
362
363
return nil
364
}
365
366
// ActionSetMethod executes an SetMethod action.
367
func (p *Page) ActionSetMethod(act *Action, out ActionData) error {
368
args := make(map[string]string)
369
370
part, err := p.getActionArg(act, "part")
371
if err != nil {
372
return err
373
}
374
375
args["method"], err = p.getActionArg(act, "method")
376
if err != nil {
377
return err
378
}
379
380
p.rules = append(p.rules, rule{
381
Action: ActionSetMethod,
382
Part: part,
383
Args: args,
384
Once: &sync.Once{},
385
})
386
387
return nil
388
}
389
390
// NavigateURL executes an ActionLoadURL actions loading a URL for the page.
391
func (p *Page) NavigateURL(action *Action, out ActionData) error {
392
url, err := p.getActionArg(action, "url")
393
if err != nil {
394
return err
395
}
396
397
if url == "" {
398
return errinvalidArguments
399
}
400
401
parsedURL, err := urlutil.ParseURL(url, true)
402
if err != nil {
403
return errkit.Newf("failed to parse url %v while creating http request", url)
404
}
405
406
// ===== parameter automerge =====
407
// while merging parameters first preference is given to target params
408
finalparams := parsedURL.Params.Clone()
409
finalparams.Merge(p.inputURL.Params.Encode())
410
parsedURL.Params = finalparams
411
412
if err := p.page.Navigate(parsedURL.String()); err != nil {
413
return errkit.Wrapf(err, "could not navigate to url %s", parsedURL.String())
414
}
415
416
p.updateLastNavigatedURL()
417
418
return nil
419
}
420
421
// RunScript runs a script on the loaded page
422
func (p *Page) RunScript(act *Action, out ActionData) error {
423
code, err := p.getActionArg(act, "code")
424
if err != nil {
425
return err
426
}
427
428
if code == "" {
429
return errinvalidArguments
430
}
431
432
hook, err := p.getActionArg(act, "hook")
433
if err != nil {
434
return err
435
}
436
437
if hook == "true" {
438
if _, err := p.page.EvalOnNewDocument(code); err != nil {
439
return err
440
}
441
}
442
443
data, err := p.page.Eval(code)
444
if err != nil {
445
return err
446
}
447
448
if data != nil && act.Name != "" {
449
out[act.Name] = data.Value.String()
450
}
451
452
return nil
453
}
454
455
// ClickElement executes click actions for an element.
456
func (p *Page) ClickElement(act *Action, out ActionData) error {
457
element, err := p.pageElementBy(act.Data)
458
if err != nil {
459
return errors.Wrap(err, errCouldNotGetElement)
460
}
461
if err = element.ScrollIntoView(); err != nil {
462
return errors.Wrap(err, errCouldNotScroll)
463
}
464
if err = element.Click(proto.InputMouseButtonLeft, 1); err != nil {
465
return errors.Wrap(err, "could not click element")
466
}
467
return nil
468
}
469
470
// KeyboardAction executes a keyboard action on the page.
471
func (p *Page) KeyboardAction(act *Action, out ActionData) error {
472
keys, err := p.getActionArg(act, "keys")
473
if err != nil {
474
return err
475
}
476
477
return p.page.Keyboard.Type([]input.Key(keys)...)
478
}
479
480
// RightClickElement executes right click actions for an element.
481
func (p *Page) RightClickElement(act *Action, out ActionData) error {
482
element, err := p.pageElementBy(act.Data)
483
if err != nil {
484
return errors.Wrap(err, errCouldNotGetElement)
485
}
486
if err = element.ScrollIntoView(); err != nil {
487
return errors.Wrap(err, errCouldNotScroll)
488
}
489
if err = element.Click(proto.InputMouseButtonRight, 1); err != nil {
490
return errors.Wrap(err, "could not right click element")
491
}
492
return nil
493
}
494
495
// Screenshot executes screenshot action on a page
496
func (p *Page) Screenshot(act *Action, out ActionData) error {
497
to, err := p.getActionArg(act, "to")
498
if err != nil {
499
return err
500
}
501
502
if to == "" {
503
to = ksuid.New().String()
504
if act.Name != "" {
505
out[act.Name] = to
506
}
507
}
508
509
var data []byte
510
511
fullpage, err := p.getActionArg(act, "fullpage")
512
if err != nil {
513
return err
514
}
515
516
if fullpage == "true" {
517
data, err = p.page.Screenshot(true, &proto.PageCaptureScreenshot{})
518
} else {
519
data, err = p.page.Screenshot(false, &proto.PageCaptureScreenshot{})
520
}
521
if err != nil {
522
return errors.Wrap(err, "could not take screenshot")
523
}
524
525
to, err = fileutil.CleanPath(to)
526
if err != nil {
527
return errkit.Newf("could not clean output screenshot path %s", to)
528
}
529
530
// allow if targetPath is child of current working directory
531
if !protocolstate.IsLfaAllowed(p.options.Options) {
532
cwd, err := os.Getwd()
533
if err != nil {
534
return errkit.Wrap(err, "could not get current working directory")
535
}
536
537
if !strings.HasPrefix(to, cwd) {
538
// writing outside of cwd requires -lfa flag
539
return ErrLFAccessDenied
540
}
541
}
542
543
mkdir, err := p.getActionArg(act, "mkdir")
544
if err != nil {
545
return err
546
}
547
548
// edgecase create directory if mkdir=true and path contains directory
549
if mkdir == "true" && stringsutil.ContainsAny(to, folderutil.UnixPathSeparator, folderutil.WindowsPathSeparator) {
550
// creates new directory if needed based on path `to`
551
// TODO: replace all permission bits with fileutil constants (https://github.com/projectdiscovery/utils/issues/113)
552
if err := os.MkdirAll(filepath.Dir(to), 0700); err != nil {
553
return errkit.Wrap(err, "failed to create directory while writing screenshot")
554
}
555
}
556
557
// actual file path to write
558
filePath := to
559
if !strings.HasSuffix(filePath, ".png") {
560
filePath += ".png"
561
}
562
563
if fileutil.FileExists(filePath) {
564
// return custom error as overwriting files is not supported
565
return errkit.Newf("failed to write screenshot, file %v already exists", filePath)
566
}
567
err = os.WriteFile(filePath, data, 0540)
568
if err != nil {
569
return errors.Wrap(err, "could not write screenshot")
570
}
571
gologger.Info().Msgf("Screenshot successfully saved at %v\n", filePath)
572
return nil
573
}
574
575
// InputElement executes input element actions for an element.
576
func (p *Page) InputElement(act *Action, out ActionData) error {
577
value, err := p.getActionArg(act, "value")
578
if err != nil {
579
return err
580
}
581
if value == "" {
582
return errinvalidArguments
583
}
584
element, err := p.pageElementBy(act.Data)
585
if err != nil {
586
return errors.Wrap(err, errCouldNotGetElement)
587
}
588
if err = element.ScrollIntoView(); err != nil {
589
return errors.Wrap(err, errCouldNotScroll)
590
}
591
if err = element.Input(value); err != nil {
592
return errors.Wrap(err, "could not input element")
593
}
594
return nil
595
}
596
597
// TimeInputElement executes time input on an element
598
func (p *Page) TimeInputElement(act *Action, out ActionData) error {
599
value, err := p.getActionArg(act, "value")
600
if err != nil {
601
return err
602
}
603
if value == "" {
604
return errinvalidArguments
605
}
606
element, err := p.pageElementBy(act.Data)
607
if err != nil {
608
return errors.Wrap(err, errCouldNotGetElement)
609
}
610
if err = element.ScrollIntoView(); err != nil {
611
return errors.Wrap(err, errCouldNotScroll)
612
}
613
t, err := time.Parse(time.RFC3339, value)
614
if err != nil {
615
return errors.Wrap(err, "could not parse time")
616
}
617
if err := element.InputTime(t); err != nil {
618
return errors.Wrap(err, "could not input element")
619
}
620
return nil
621
}
622
623
// SelectInputElement executes select input statement action on a element
624
func (p *Page) SelectInputElement(act *Action, out ActionData) error {
625
value, err := p.getActionArg(act, "value")
626
if err != nil {
627
return err
628
}
629
if value == "" {
630
return errinvalidArguments
631
}
632
element, err := p.pageElementBy(act.Data)
633
if err != nil {
634
return errors.Wrap(err, errCouldNotGetElement)
635
}
636
if err = element.ScrollIntoView(); err != nil {
637
return errors.Wrap(err, errCouldNotScroll)
638
}
639
640
var selectedBool bool
641
642
selected, err := p.getActionArg(act, "selected")
643
if err != nil {
644
return err
645
}
646
647
if selected == "true" {
648
selectedBool = true
649
}
650
651
selector, err := p.getActionArg(act, "selector")
652
if err != nil {
653
return err
654
}
655
656
if err := element.Select([]string{value}, selectedBool, selectorBy(selector)); err != nil {
657
return errors.Wrap(err, "could not select input")
658
}
659
660
return nil
661
}
662
663
// WaitPageLifecycleEvent waits for specified page lifecycle event name
664
func (p *Page) WaitPageLifecycleEvent(act *Action, out ActionData, event proto.PageLifecycleEventName) error {
665
fn, err := getNavigationFunc(p, act, event)
666
if err != nil {
667
return err
668
}
669
670
fn()
671
672
// log the navigated request (even if it is a redirect)
673
p.updateLastNavigatedURL()
674
675
return nil
676
}
677
678
// WaitStable waits until the page is stable
679
func (p *Page) WaitStable(act *Action, out ActionData) error {
680
dur := time.Second // default stable page duration: 1s
681
682
timeout, err := getTimeout(p, act)
683
if err != nil {
684
return errors.Wrap(err, "Wrong timeout given")
685
}
686
687
argDur := act.Data["duration"]
688
if argDur != "" {
689
dur, err = time.ParseDuration(argDur)
690
if err != nil {
691
dur = time.Second
692
}
693
}
694
695
if err := p.page.Timeout(timeout).WaitStable(dur); err != nil {
696
return err
697
}
698
699
// log the navigated request (even if it is a redirect)
700
p.updateLastNavigatedURL()
701
702
return nil
703
}
704
705
// GetResource gets a resource from an element from page.
706
func (p *Page) GetResource(act *Action, out ActionData) error {
707
element, err := p.pageElementBy(act.Data)
708
if err != nil {
709
return errors.Wrap(err, errCouldNotGetElement)
710
}
711
resource, err := element.Resource()
712
if err != nil {
713
return errors.Wrap(err, "could not get src for element")
714
}
715
if act.Name != "" {
716
out[act.Name] = string(resource)
717
}
718
return nil
719
}
720
721
// FilesInput acts with a file input element on page
722
func (p *Page) FilesInput(act *Action, out ActionData) error {
723
element, err := p.pageElementBy(act.Data)
724
if err != nil {
725
return errors.Wrap(err, errCouldNotGetElement)
726
}
727
728
if err = element.ScrollIntoView(); err != nil {
729
return errors.Wrap(err, errCouldNotScroll)
730
}
731
732
value, err := p.getActionArg(act, "value")
733
if err != nil {
734
return err
735
}
736
filesPaths := strings.Split(value, ",")
737
738
if err := element.SetFiles(filesPaths); err != nil {
739
return errors.Wrap(err, "could not set files")
740
}
741
742
return nil
743
}
744
745
// ExtractElement extracts from an element on the page.
746
func (p *Page) ExtractElement(act *Action, out ActionData) error {
747
element, err := p.pageElementBy(act.Data)
748
if err != nil {
749
return errors.Wrap(err, errCouldNotGetElement)
750
}
751
752
if err = element.ScrollIntoView(); err != nil {
753
return errors.Wrap(err, errCouldNotScroll)
754
}
755
756
target, err := p.getActionArg(act, "target")
757
if err != nil {
758
return err
759
}
760
761
switch target {
762
case "attribute":
763
attribute, err := p.getActionArg(act, "attribute")
764
if err != nil {
765
return err
766
}
767
768
if attribute == "" {
769
return errors.New("attribute can't be empty")
770
}
771
772
attrValue, err := element.Attribute(attribute)
773
if err != nil {
774
return errors.Wrap(err, "could not get attribute")
775
}
776
777
if act.Name != "" {
778
out[act.Name] = *attrValue
779
}
780
default:
781
text, err := element.Text()
782
if err != nil {
783
return errors.Wrap(err, "could not get element text node")
784
}
785
786
if act.Name != "" {
787
out[act.Name] = text
788
}
789
}
790
return nil
791
}
792
793
// WaitEvent waits for an event to happen on the page.
794
func (p *Page) WaitEvent(act *Action, out ActionData) (func() error, error) {
795
event, err := p.getActionArg(act, "event")
796
if err != nil {
797
return nil, err
798
}
799
800
if event == "" {
801
return nil, errors.New("event not recognized")
802
}
803
804
var waitEvent proto.Event
805
806
gotType := proto.GetType(event)
807
if gotType == nil {
808
return nil, errkit.Newf("event %q does not exist", event)
809
}
810
811
tmp, ok := reflect.New(gotType).Interface().(proto.Event)
812
if !ok {
813
return nil, errkit.Newf("event %q is not a page event", event)
814
}
815
816
waitEvent = tmp
817
818
// allow user to specify max-duration for wait-event
819
maxDuration, err := getTimeParameter(p, act, "max-duration", 5, time.Second)
820
if err != nil {
821
return nil, err
822
}
823
824
// Just wait the event to happen
825
waitFunc := func() (err error) {
826
// execute actual wait event
827
ctx, cancel := context.WithTimeoutCause(context.Background(), maxDuration, ErrActionExecDealine)
828
defer cancel()
829
830
err = contextutil.ExecFunc(ctx, p.page.WaitEvent(waitEvent))
831
832
return
833
}
834
835
return waitFunc, nil
836
}
837
838
// HandleDialog handles JavaScript dialog (alert, confirm, prompt, or onbeforeunload).
839
func (p *Page) HandleDialog(act *Action, out ActionData) error {
840
maxDuration, err := getTimeParameter(p, act, "max-duration", 10, time.Second)
841
if err != nil {
842
return err
843
}
844
845
ctx, cancel := context.WithTimeout(context.Background(), maxDuration)
846
defer cancel()
847
848
wait, handle := p.page.HandleDialog()
849
fn := func() (*proto.PageJavascriptDialogOpening, error) {
850
dialog := wait()
851
err := handle(&proto.PageHandleJavaScriptDialog{
852
Accept: true,
853
PromptText: "",
854
})
855
856
return dialog, err
857
}
858
859
dialog, err := contextutil.ExecFuncWithTwoReturns(ctx, fn)
860
if err == nil && act.Name != "" {
861
out[act.Name] = true
862
out[act.Name+"_type"] = string(dialog.Type)
863
out[act.Name+"_message"] = dialog.Message
864
}
865
866
return nil
867
}
868
869
// pageElementBy returns a page element from a variety of inputs.
870
//
871
// Supported values for by: r -> selector & regex, x -> xpath, js -> eval js,
872
// search => query, default ("") => selector.
873
func (p *Page) pageElementBy(data map[string]string) (*rod.Element, error) {
874
by, ok := data["by"]
875
if !ok {
876
by = ""
877
}
878
page := p.page
879
880
switch by {
881
case "r", "regex":
882
return page.ElementR(data["selector"], data["regex"])
883
case "x", "xpath":
884
return page.ElementX(data["xpath"])
885
case "js":
886
return page.ElementByJS(&rod.EvalOptions{JS: data["js"]})
887
case "search":
888
elms, err := page.Search(data["query"])
889
if err != nil {
890
return nil, err
891
}
892
893
if elms.First != nil {
894
return elms.First, nil
895
}
896
return nil, errors.New("no such element")
897
default:
898
return page.Element(data["selector"])
899
}
900
}
901
902
// DebugAction enables debug action on a page.
903
func (p *Page) DebugAction(act *Action, out ActionData) error {
904
p.instance.browser.engine.SlowMotion(5 * time.Second)
905
p.instance.browser.engine.Trace(true)
906
return nil
907
}
908
909
// SleepAction sleeps on the page for a specified duration
910
func (p *Page) SleepAction(act *Action, out ActionData) error {
911
duration, err := getTimeParameter(p, act, "duration", 5, time.Second)
912
if err != nil {
913
return err
914
}
915
916
time.Sleep(duration)
917
918
return nil
919
}
920
921
// selectorBy returns a selector from a representation.
922
func selectorBy(selector string) rod.SelectorType {
923
switch selector {
924
case "r":
925
return rod.SelectorTypeRegex
926
case "css":
927
return rod.SelectorTypeCSSSector
928
case "regex":
929
return rod.SelectorTypeRegex
930
default:
931
return rod.SelectorTypeText
932
}
933
}
934
935
func (p *Page) getActionArg(action *Action, arg string) (string, error) {
936
var err error
937
938
argValue := action.GetArg(arg)
939
940
if p.instance.interactsh != nil {
941
var interactshURLs []string
942
argValue, interactshURLs = p.instance.interactsh.Replace(argValue, p.InteractshURLs)
943
p.addInteractshURL(interactshURLs...)
944
}
945
946
exprs := getExpressions(argValue, p.variables)
947
948
err = expressions.ContainsUnresolvedVariables(exprs...)
949
if err != nil {
950
return "", errkit.Wrapf(err, "argument %q, value: %q", arg, argValue)
951
}
952
953
argValue, err = expressions.Evaluate(argValue, p.variables)
954
if err != nil {
955
return "", fmt.Errorf("could not get value for argument %q: %s", arg, err)
956
}
957
958
return argValue, nil
959
}
960
961