github.com/projectdiscovery/nuclei/v2@v2.9.15/pkg/protocols/headless/engine/page_actions.go (about)

     1  package engine
     2  
     3  import (
     4  	"context"
     5  	"os"
     6  	"path/filepath"
     7  	"strconv"
     8  	"strings"
     9  	"sync"
    10  	"time"
    11  
    12  	"github.com/go-rod/rod"
    13  	"github.com/go-rod/rod/lib/input"
    14  	"github.com/go-rod/rod/lib/proto"
    15  	"github.com/go-rod/rod/lib/utils"
    16  	"github.com/pkg/errors"
    17  	"github.com/projectdiscovery/gologger"
    18  	"github.com/projectdiscovery/nuclei/v2/pkg/protocols/common/contextargs"
    19  	"github.com/projectdiscovery/nuclei/v2/pkg/protocols/common/expressions"
    20  	"github.com/projectdiscovery/nuclei/v2/pkg/protocols/common/generators"
    21  	"github.com/projectdiscovery/nuclei/v2/pkg/protocols/common/utils/vardump"
    22  	protocolutils "github.com/projectdiscovery/nuclei/v2/pkg/protocols/utils"
    23  	httputil "github.com/projectdiscovery/nuclei/v2/pkg/protocols/utils/http"
    24  	errorutil "github.com/projectdiscovery/utils/errors"
    25  	fileutil "github.com/projectdiscovery/utils/file"
    26  	folderutil "github.com/projectdiscovery/utils/folder"
    27  	stringsutil "github.com/projectdiscovery/utils/strings"
    28  	urlutil "github.com/projectdiscovery/utils/url"
    29  	"github.com/segmentio/ksuid"
    30  )
    31  
    32  var (
    33  	errinvalidArguments = errorutil.New("invalid arguments provided")
    34  	ErrLFAccessDenied   = errorutil.New("Use -allow-local-file-access flag to enable local file access")
    35  )
    36  
    37  const (
    38  	errCouldNotGetElement  = "could not get element"
    39  	errCouldNotScroll      = "could not scroll into view"
    40  	errElementDidNotAppear = "Element did not appear in the given amount of time"
    41  )
    42  
    43  // ExecuteActions executes a list of actions on a page.
    44  func (p *Page) ExecuteActions(input *contextargs.Context, actions []*Action, variables map[string]interface{}) (map[string]string, error) {
    45  	outData := make(map[string]string)
    46  	var err error
    47  	for _, act := range actions {
    48  		switch act.ActionType.ActionType {
    49  		case ActionNavigate:
    50  			err = p.NavigateURL(act, outData, variables)
    51  		case ActionScript:
    52  			err = p.RunScript(act, outData)
    53  		case ActionClick:
    54  			err = p.ClickElement(act, outData)
    55  		case ActionRightClick:
    56  			err = p.RightClickElement(act, outData)
    57  		case ActionTextInput:
    58  			err = p.InputElement(act, outData)
    59  		case ActionScreenshot:
    60  			err = p.Screenshot(act, outData)
    61  		case ActionTimeInput:
    62  			err = p.TimeInputElement(act, outData)
    63  		case ActionSelectInput:
    64  			err = p.SelectInputElement(act, outData)
    65  		case ActionWaitLoad:
    66  			err = p.WaitLoad(act, outData)
    67  		case ActionGetResource:
    68  			err = p.GetResource(act, outData)
    69  		case ActionExtract:
    70  			err = p.ExtractElement(act, outData)
    71  		case ActionWaitEvent:
    72  			err = p.WaitEvent(act, outData)
    73  		case ActionFilesInput:
    74  			if p.options.Options.AllowLocalFileAccess {
    75  				err = p.FilesInput(act, outData)
    76  			} else {
    77  				err = ErrLFAccessDenied
    78  			}
    79  		case ActionAddHeader:
    80  			err = p.ActionAddHeader(act, outData)
    81  		case ActionSetHeader:
    82  			err = p.ActionSetHeader(act, outData)
    83  		case ActionDeleteHeader:
    84  			err = p.ActionDeleteHeader(act, outData)
    85  		case ActionSetBody:
    86  			err = p.ActionSetBody(act, outData)
    87  		case ActionSetMethod:
    88  			err = p.ActionSetMethod(act, outData)
    89  		case ActionKeyboard:
    90  			err = p.KeyboardAction(act, outData)
    91  		case ActionDebug:
    92  			err = p.DebugAction(act, outData)
    93  		case ActionSleep:
    94  			err = p.SleepAction(act, outData)
    95  		case ActionWaitVisible:
    96  			err = p.WaitVisible(act, outData)
    97  		default:
    98  			continue
    99  		}
   100  		if err != nil {
   101  			return nil, errors.Wrap(err, "error occurred executing action")
   102  		}
   103  	}
   104  	return outData, nil
   105  }
   106  
   107  type rule struct {
   108  	*sync.Once
   109  	Action ActionType
   110  	Part   string
   111  	Args   map[string]string
   112  }
   113  
   114  // WaitVisible waits until an element appears.
   115  func (p *Page) WaitVisible(act *Action, out map[string]string) error {
   116  	timeout, err := getTimeout(p, act)
   117  	if err != nil {
   118  		return errors.Wrap(err, "Wrong timeout given")
   119  	}
   120  
   121  	pollTime, err := getPollTime(p, act)
   122  	if err != nil {
   123  		return errors.Wrap(err, "Wrong polling time given")
   124  	}
   125  
   126  	element, _ := p.Sleeper(pollTime, timeout).
   127  		Timeout(timeout).
   128  		pageElementBy(act.Data)
   129  
   130  	if element != nil {
   131  		if err := element.WaitVisible(); err != nil {
   132  			return errors.Wrap(err, errElementDidNotAppear)
   133  		}
   134  	} else {
   135  		return errors.New(errElementDidNotAppear)
   136  	}
   137  
   138  	return nil
   139  }
   140  
   141  func (p *Page) Sleeper(pollTimeout, timeout time.Duration) *Page {
   142  	page := *p
   143  	page.page = page.Page().Sleeper(func() utils.Sleeper {
   144  		return createBackOffSleeper(pollTimeout, timeout)
   145  	})
   146  	return &page
   147  }
   148  
   149  func (p *Page) Timeout(timeout time.Duration) *Page {
   150  	page := *p
   151  	page.page = page.Page().Timeout(timeout)
   152  	return &page
   153  }
   154  
   155  func createBackOffSleeper(pollTimeout, timeout time.Duration) utils.Sleeper {
   156  	backoffSleeper := utils.BackoffSleeper(pollTimeout, timeout, func(duration time.Duration) time.Duration {
   157  		return duration
   158  	})
   159  
   160  	return func(ctx context.Context) error {
   161  		if ctx.Err() != nil {
   162  			return ctx.Err()
   163  		}
   164  
   165  		return backoffSleeper(ctx)
   166  	}
   167  }
   168  
   169  func getTimeout(p *Page, act *Action) (time.Duration, error) {
   170  	return geTimeParameter(p, act, "timeout", 3, time.Second)
   171  }
   172  
   173  func getPollTime(p *Page, act *Action) (time.Duration, error) {
   174  	return geTimeParameter(p, act, "pollTime", 100, time.Millisecond)
   175  }
   176  
   177  func geTimeParameter(p *Page, act *Action, parameterName string, defaultValue time.Duration, duration time.Duration) (time.Duration, error) {
   178  	pollTimeString := p.getActionArgWithDefaultValues(act, parameterName)
   179  	if pollTimeString == "" {
   180  		return defaultValue * duration, nil
   181  	}
   182  	timeout, err := strconv.Atoi(pollTimeString)
   183  	if err != nil {
   184  		return time.Duration(0), err
   185  	}
   186  	return time.Duration(timeout) * duration, nil
   187  }
   188  
   189  // ActionAddHeader executes a AddHeader action.
   190  func (p *Page) ActionAddHeader(act *Action, out map[string]string /*TODO review unused parameter*/) error {
   191  	in := p.getActionArgWithDefaultValues(act, "part")
   192  
   193  	args := make(map[string]string)
   194  	args["key"] = p.getActionArgWithDefaultValues(act, "key")
   195  	args["value"] = p.getActionArgWithDefaultValues(act, "value")
   196  	p.rules = append(p.rules, rule{Action: ActionAddHeader, Part: in, Args: args})
   197  	return nil
   198  }
   199  
   200  // ActionSetHeader executes a SetHeader action.
   201  func (p *Page) ActionSetHeader(act *Action, out map[string]string /*TODO review unused parameter*/) error {
   202  	in := p.getActionArgWithDefaultValues(act, "part")
   203  
   204  	args := make(map[string]string)
   205  	args["key"] = p.getActionArgWithDefaultValues(act, "key")
   206  	args["value"] = p.getActionArgWithDefaultValues(act, "value")
   207  	p.rules = append(p.rules, rule{Action: ActionSetHeader, Part: in, Args: args})
   208  	return nil
   209  }
   210  
   211  // ActionDeleteHeader executes a DeleteHeader action.
   212  func (p *Page) ActionDeleteHeader(act *Action, out map[string]string /*TODO review unused parameter*/) error {
   213  	in := p.getActionArgWithDefaultValues(act, "part")
   214  
   215  	args := make(map[string]string)
   216  	args["key"] = p.getActionArgWithDefaultValues(act, "key")
   217  	p.rules = append(p.rules, rule{Action: ActionDeleteHeader, Part: in, Args: args})
   218  	return nil
   219  }
   220  
   221  // ActionSetBody executes a SetBody action.
   222  func (p *Page) ActionSetBody(act *Action, out map[string]string) error {
   223  	in := p.getActionArgWithDefaultValues(act, "part")
   224  
   225  	args := make(map[string]string)
   226  	args["body"] = p.getActionArgWithDefaultValues(act, "body")
   227  	p.rules = append(p.rules, rule{Action: ActionSetBody, Part: in, Args: args})
   228  	return nil
   229  }
   230  
   231  // ActionSetMethod executes an SetMethod action.
   232  func (p *Page) ActionSetMethod(act *Action, out map[string]string) error {
   233  	in := p.getActionArgWithDefaultValues(act, "part")
   234  
   235  	args := make(map[string]string)
   236  	args["method"] = p.getActionArgWithDefaultValues(act, "method")
   237  	p.rules = append(p.rules, rule{Action: ActionSetMethod, Part: in, Args: args, Once: &sync.Once{}})
   238  	return nil
   239  }
   240  
   241  // NavigateURL executes an ActionLoadURL actions loading a URL for the page.
   242  func (p *Page) NavigateURL(action *Action, out map[string]string, allvars map[string]interface{}) error {
   243  	// input <- is input url from cli
   244  	// target <- is the url from template (ex: {{BaseURL}}/test)
   245  	input, err := urlutil.Parse(p.input.MetaInput.Input)
   246  	if err != nil {
   247  		return errorutil.NewWithErr(err).Msgf("could not parse url %s", p.input.MetaInput.Input)
   248  	}
   249  	target := p.getActionArgWithDefaultValues(action, "url")
   250  	if target == "" {
   251  		return errinvalidArguments
   252  	}
   253  
   254  	// if target contains port ex: {{BaseURL}}:8080 use port specified in input
   255  	input, target = httputil.UpdateURLPortFromPayload(input, target)
   256  	hasTrailingSlash := httputil.HasTrailingSlash(target)
   257  
   258  	// create vars from input url
   259  	defaultReqVars := protocolutils.GenerateVariables(input, hasTrailingSlash, contextargs.GenerateVariables(p.input))
   260  	// merge all variables
   261  	// Note: ideally we should evaluate all available variables with reqvars
   262  	// but due to cyclic dependency between packages `engine` and `protocols`
   263  	// allvars are evaluated,merged and passed from headless package itself
   264  	// TODO: remove cyclic dependency between packages `engine` and `protocols`
   265  	allvars = generators.MergeMaps(allvars, defaultReqVars)
   266  
   267  	if vardump.EnableVarDump {
   268  		gologger.Debug().Msgf("Final Protocol request variables: \n%s\n", vardump.DumpVariables(allvars))
   269  	}
   270  
   271  	// Evaluate the target url with all variables
   272  	target, err = expressions.Evaluate(target, allvars)
   273  	if err != nil {
   274  		return errorutil.NewWithErr(err).Msgf("could not evaluate url %s", target)
   275  	}
   276  
   277  	reqURL, err := urlutil.ParseURL(target, true)
   278  	if err != nil {
   279  		return errorutil.NewWithTag("http", "failed to parse url %v while creating http request", target)
   280  	}
   281  
   282  	// ===== parameter automerge =====
   283  	// while merging parameters first preference is given to target params
   284  	finalparams := input.Params.Clone()
   285  	finalparams.Merge(reqURL.Params.Encode())
   286  	reqURL.Params = finalparams
   287  
   288  	// log all navigated requests
   289  	p.instance.requestLog[action.GetArg("url")] = reqURL.String()
   290  
   291  	if err := p.page.Navigate(reqURL.String()); err != nil {
   292  		return errorutil.NewWithErr(err).Msgf("could not navigate to url %s", reqURL.String())
   293  	}
   294  	return nil
   295  }
   296  
   297  // RunScript runs a script on the loaded page
   298  func (p *Page) RunScript(action *Action, out map[string]string) error {
   299  	code := p.getActionArgWithDefaultValues(action, "code")
   300  	if code == "" {
   301  		return errinvalidArguments
   302  	}
   303  	if p.getActionArgWithDefaultValues(action, "hook") == "true" {
   304  		if _, err := p.page.EvalOnNewDocument(code); err != nil {
   305  			return err
   306  		}
   307  	}
   308  	data, err := p.page.Eval(code)
   309  	if err != nil {
   310  		return err
   311  	}
   312  	if data != nil && action.Name != "" {
   313  		out[action.Name] = data.Value.String()
   314  	}
   315  	return nil
   316  }
   317  
   318  // ClickElement executes click actions for an element.
   319  func (p *Page) ClickElement(act *Action, out map[string]string /*TODO review unused parameter*/) error {
   320  	element, err := p.pageElementBy(act.Data)
   321  	if err != nil {
   322  		return errors.Wrap(err, errCouldNotGetElement)
   323  	}
   324  	if err = element.ScrollIntoView(); err != nil {
   325  		return errors.Wrap(err, errCouldNotScroll)
   326  	}
   327  	if err = element.Click(proto.InputMouseButtonLeft, 1); err != nil {
   328  		return errors.Wrap(err, "could not click element")
   329  	}
   330  	return nil
   331  }
   332  
   333  // KeyboardAction executes a keyboard action on the page.
   334  func (p *Page) KeyboardAction(act *Action, out map[string]string /*TODO review unused parameter*/) error {
   335  	return p.page.Keyboard.Type([]input.Key(p.getActionArgWithDefaultValues(act, "keys"))...)
   336  }
   337  
   338  // RightClickElement executes right click actions for an element.
   339  func (p *Page) RightClickElement(act *Action, out map[string]string /*TODO review unused parameter*/) error {
   340  	element, err := p.pageElementBy(act.Data)
   341  	if err != nil {
   342  		return errors.Wrap(err, errCouldNotGetElement)
   343  	}
   344  	if err = element.ScrollIntoView(); err != nil {
   345  		return errors.Wrap(err, errCouldNotScroll)
   346  	}
   347  	if err = element.Click(proto.InputMouseButtonRight, 1); err != nil {
   348  		return errors.Wrap(err, "could not right click element")
   349  	}
   350  	return nil
   351  }
   352  
   353  // Screenshot executes screenshot action on a page
   354  func (p *Page) Screenshot(act *Action, out map[string]string) error {
   355  	to := p.getActionArgWithDefaultValues(act, "to")
   356  	if to == "" {
   357  		to = ksuid.New().String()
   358  		if act.Name != "" {
   359  			out[act.Name] = to
   360  		}
   361  	}
   362  	var data []byte
   363  	var err error
   364  	if p.getActionArgWithDefaultValues(act, "fullpage") == "true" {
   365  		data, err = p.page.Screenshot(true, &proto.PageCaptureScreenshot{})
   366  	} else {
   367  		data, err = p.page.Screenshot(false, &proto.PageCaptureScreenshot{})
   368  	}
   369  	if err != nil {
   370  		return errors.Wrap(err, "could not take screenshot")
   371  	}
   372  	if p.getActionArgWithDefaultValues(act, "mkdir") == "true" && stringsutil.ContainsAny(to, folderutil.UnixPathSeparator, folderutil.WindowsPathSeparator) {
   373  		// creates new directory if needed based on path `to`
   374  		// TODO: replace all permission bits with fileutil constants (https://github.com/projectdiscovery/utils/issues/113)
   375  		if err := os.MkdirAll(filepath.Dir(to), 0700); err != nil {
   376  			return errorutil.NewWithErr(err).Msgf("failed to create directory while writing screenshot")
   377  		}
   378  	}
   379  	filePath := to
   380  	if !strings.HasSuffix(to, ".png") {
   381  		filePath += ".png"
   382  	}
   383  
   384  	if fileutil.FileExists(filePath) {
   385  		// return custom error as overwriting files is not supported
   386  		return errorutil.NewWithTag("screenshot", "failed to write screenshot, file %v already exists", filePath)
   387  	}
   388  	err = os.WriteFile(filePath, data, 0540)
   389  	if err != nil {
   390  		return errors.Wrap(err, "could not write screenshot")
   391  	}
   392  	gologger.Info().Msgf("Screenshot successfully saved at %v\n", filePath)
   393  	return nil
   394  }
   395  
   396  // InputElement executes input element actions for an element.
   397  func (p *Page) InputElement(act *Action, out map[string]string /*TODO review unused parameter*/) error {
   398  	value := p.getActionArgWithDefaultValues(act, "value")
   399  	if value == "" {
   400  		return errinvalidArguments
   401  	}
   402  	element, err := p.pageElementBy(act.Data)
   403  	if err != nil {
   404  		return errors.Wrap(err, errCouldNotGetElement)
   405  	}
   406  	if err = element.ScrollIntoView(); err != nil {
   407  		return errors.Wrap(err, errCouldNotScroll)
   408  	}
   409  	if err = element.Input(value); err != nil {
   410  		return errors.Wrap(err, "could not input element")
   411  	}
   412  	return nil
   413  }
   414  
   415  // TimeInputElement executes time input on an element
   416  func (p *Page) TimeInputElement(act *Action, out map[string]string /*TODO review unused parameter*/) error {
   417  	value := p.getActionArgWithDefaultValues(act, "value")
   418  	if value == "" {
   419  		return errinvalidArguments
   420  	}
   421  	element, err := p.pageElementBy(act.Data)
   422  	if err != nil {
   423  		return errors.Wrap(err, errCouldNotGetElement)
   424  	}
   425  	if err = element.ScrollIntoView(); err != nil {
   426  		return errors.Wrap(err, errCouldNotScroll)
   427  	}
   428  	t, err := time.Parse(time.RFC3339, value)
   429  	if err != nil {
   430  		return errors.Wrap(err, "could not parse time")
   431  	}
   432  	if err := element.InputTime(t); err != nil {
   433  		return errors.Wrap(err, "could not input element")
   434  	}
   435  	return nil
   436  }
   437  
   438  // SelectInputElement executes select input statement action on a element
   439  func (p *Page) SelectInputElement(act *Action, out map[string]string /*TODO review unused parameter*/) error {
   440  	value := p.getActionArgWithDefaultValues(act, "value")
   441  	if value == "" {
   442  		return errinvalidArguments
   443  	}
   444  	element, err := p.pageElementBy(act.Data)
   445  	if err != nil {
   446  		return errors.Wrap(err, errCouldNotGetElement)
   447  	}
   448  	if err = element.ScrollIntoView(); err != nil {
   449  		return errors.Wrap(err, errCouldNotScroll)
   450  	}
   451  
   452  	selectedBool := false
   453  	if p.getActionArgWithDefaultValues(act, "selected") == "true" {
   454  		selectedBool = true
   455  	}
   456  	by := p.getActionArgWithDefaultValues(act, "selector")
   457  	if err := element.Select([]string{value}, selectedBool, selectorBy(by)); err != nil {
   458  		return errors.Wrap(err, "could not select input")
   459  	}
   460  	return nil
   461  }
   462  
   463  // WaitLoad waits for the page to load
   464  func (p *Page) WaitLoad(act *Action, out map[string]string /*TODO review unused parameter*/) error {
   465  	p.page.Timeout(2 * time.Second).WaitNavigation(proto.PageLifecycleEventNameFirstMeaningfulPaint)()
   466  
   467  	// Wait for the window.onload event and also wait for the network requests
   468  	// to become idle for a maximum duration of 3 seconds. If the requests
   469  	// do not finish,
   470  	if err := p.page.WaitLoad(); err != nil {
   471  		return errors.Wrap(err, "could not wait load event")
   472  	}
   473  	_ = p.page.WaitIdle(1 * time.Second)
   474  	return nil
   475  }
   476  
   477  // GetResource gets a resource from an element from page.
   478  func (p *Page) GetResource(act *Action, out map[string]string) error {
   479  	element, err := p.pageElementBy(act.Data)
   480  	if err != nil {
   481  		return errors.Wrap(err, errCouldNotGetElement)
   482  	}
   483  	resource, err := element.Resource()
   484  	if err != nil {
   485  		return errors.Wrap(err, "could not get src for element")
   486  	}
   487  	if act.Name != "" {
   488  		out[act.Name] = string(resource)
   489  	}
   490  	return nil
   491  }
   492  
   493  // FilesInput acts with a file input element on page
   494  func (p *Page) FilesInput(act *Action, out map[string]string /*TODO review unused parameter*/) error {
   495  	element, err := p.pageElementBy(act.Data)
   496  	if err != nil {
   497  		return errors.Wrap(err, errCouldNotGetElement)
   498  	}
   499  	if err = element.ScrollIntoView(); err != nil {
   500  		return errors.Wrap(err, errCouldNotScroll)
   501  	}
   502  	value := p.getActionArgWithDefaultValues(act, "value")
   503  	filesPaths := strings.Split(value, ",")
   504  	if err := element.SetFiles(filesPaths); err != nil {
   505  		return errors.Wrap(err, "could not set files")
   506  	}
   507  	return nil
   508  }
   509  
   510  // ExtractElement extracts from an element on the page.
   511  func (p *Page) ExtractElement(act *Action, out map[string]string) error {
   512  	element, err := p.pageElementBy(act.Data)
   513  	if err != nil {
   514  		return errors.Wrap(err, errCouldNotGetElement)
   515  	}
   516  	if err = element.ScrollIntoView(); err != nil {
   517  		return errors.Wrap(err, errCouldNotScroll)
   518  	}
   519  	switch p.getActionArgWithDefaultValues(act, "target") {
   520  	case "attribute":
   521  		attrName := p.getActionArgWithDefaultValues(act, "attribute")
   522  		if attrName == "" {
   523  			return errors.New("attribute can't be empty")
   524  		}
   525  		attrValue, err := element.Attribute(attrName)
   526  		if err != nil {
   527  			return errors.Wrap(err, "could not get attribute")
   528  		}
   529  		if act.Name != "" {
   530  			out[act.Name] = *attrValue
   531  		}
   532  	default:
   533  		text, err := element.Text()
   534  		if err != nil {
   535  			return errors.Wrap(err, "could not get element text node")
   536  		}
   537  		if act.Name != "" {
   538  			out[act.Name] = text
   539  		}
   540  	}
   541  	return nil
   542  }
   543  
   544  type protoEvent struct {
   545  	event string
   546  }
   547  
   548  // ProtoEvent returns the cdp.Event.Method
   549  func (p *protoEvent) ProtoEvent() string {
   550  	return p.event
   551  }
   552  
   553  // WaitEvent waits for an event to happen on the page.
   554  func (p *Page) WaitEvent(act *Action, out map[string]string /*TODO review unused parameter*/) error {
   555  	event := p.getActionArgWithDefaultValues(act, "event")
   556  	if event == "" {
   557  		return errors.New("event not recognized")
   558  	}
   559  	protoEvent := &protoEvent{event: event}
   560  
   561  	// Uses another instance in order to be able to chain the timeout only to the wait operation
   562  	pageCopy := p.page
   563  	timeout := p.getActionArg(act, "timeout")
   564  	if timeout != "" {
   565  		ts, err := strconv.Atoi(timeout)
   566  		if err != nil {
   567  			return errors.Wrap(err, "could not get timeout")
   568  		}
   569  		if ts > 0 {
   570  			pageCopy = p.page.Timeout(time.Duration(ts) * time.Second)
   571  		}
   572  	}
   573  	// Just wait the event to happen
   574  	pageCopy.WaitEvent(protoEvent)()
   575  	return nil
   576  }
   577  
   578  // pageElementBy returns a page element from a variety of inputs.
   579  //
   580  // Supported values for by: r -> selector & regex, x -> xpath, js -> eval js,
   581  // search => query, default ("") => selector.
   582  func (p *Page) pageElementBy(data map[string]string) (*rod.Element, error) {
   583  	by, ok := data["by"]
   584  	if !ok {
   585  		by = ""
   586  	}
   587  	page := p.page
   588  
   589  	switch by {
   590  	case "r", "regex":
   591  		return page.ElementR(data["selector"], data["regex"])
   592  	case "x", "xpath":
   593  		return page.ElementX(data["xpath"])
   594  	case "js":
   595  		return page.ElementByJS(&rod.EvalOptions{JS: data["js"]})
   596  	case "search":
   597  		elms, err := page.Search(data["query"])
   598  		if err != nil {
   599  			return nil, err
   600  		}
   601  
   602  		if elms.First != nil {
   603  			return elms.First, nil
   604  		}
   605  		return nil, errors.New("no such element")
   606  	default:
   607  		return page.Element(data["selector"])
   608  	}
   609  }
   610  
   611  // DebugAction enables debug action on a page.
   612  func (p *Page) DebugAction(act *Action, out map[string]string /*TODO review unused parameter*/) error {
   613  	p.instance.browser.engine.SlowMotion(5 * time.Second)
   614  	p.instance.browser.engine.Trace(true)
   615  	return nil
   616  }
   617  
   618  // SleepAction sleeps on the page for a specified duration
   619  func (p *Page) SleepAction(act *Action, out map[string]string /*TODO review unused parameter*/) error {
   620  	seconds := act.Data["duration"]
   621  	if seconds == "" {
   622  		seconds = "5"
   623  	}
   624  	parsed, err := strconv.Atoi(seconds)
   625  	if err != nil {
   626  		return err
   627  	}
   628  	time.Sleep(time.Duration(parsed) * time.Second)
   629  	return nil
   630  }
   631  
   632  // selectorBy returns a selector from a representation.
   633  func selectorBy(selector string) rod.SelectorType {
   634  	switch selector {
   635  	case "r":
   636  		return rod.SelectorTypeRegex
   637  	case "css":
   638  		return rod.SelectorTypeCSSSector
   639  	case "regex":
   640  		return rod.SelectorTypeRegex
   641  	default:
   642  		return rod.SelectorTypeText
   643  	}
   644  }
   645  
   646  func (p *Page) getActionArg(action *Action, arg string) string {
   647  	return p.getActionArgWithValues(action, arg, nil)
   648  }
   649  
   650  func (p *Page) getActionArgWithDefaultValues(action *Action, arg string) string {
   651  	return p.getActionArgWithValues(action, arg, generators.MergeMaps(
   652  		generators.BuildPayloadFromOptions(p.instance.browser.options),
   653  		p.payloads,
   654  	))
   655  }
   656  
   657  func (p *Page) getActionArgWithValues(action *Action, arg string, values map[string]interface{}) string {
   658  	argValue := action.GetArg(arg)
   659  	argValue = replaceWithValues(argValue, values)
   660  	if p.instance.interactsh != nil {
   661  		var interactshURLs []string
   662  		argValue, interactshURLs = p.instance.interactsh.Replace(argValue, p.InteractshURLs)
   663  		p.addInteractshURL(interactshURLs...)
   664  	}
   665  	return argValue
   666  }