github.com/web-platform-tests/wpt.fyi@v0.0.0-20240530210107-70cf978996f1/webdriver/webdriver.go (about)

     1  package webdriver
     2  
     3  import (
     4  	"encoding/json"
     5  	"flag"
     6  	"fmt"
     7  	"io/ioutil"
     8  	"log"
     9  	"os"
    10  	"runtime"
    11  	"testing"
    12  	"time"
    13  
    14  	"github.com/phayes/freeport"
    15  	"github.com/tebeka/selenium"
    16  )
    17  
    18  // Flags
    19  var (
    20  	debug            = flag.Bool("debug", false, "Turn on debug logging")
    21  	browser          = flag.String("browser", "firefox", "Which browser to run the tests with")
    22  	startFrameBuffer = flag.Bool("frame_buffer", frameBufferDefault(), "Whether to use a frame buffer")
    23  )
    24  
    25  const (
    26  	// LongTimeout is the timeout for waiting the full page to load, with
    27  	// data coming from Datastore. You may not need this if you only need
    28  	// to wait for the initial Polymer rendering.
    29  	LongTimeout = time.Second * 30
    30  )
    31  
    32  func frameBufferDefault() bool {
    33  	return runtime.GOOS != "darwin"
    34  }
    35  
    36  func pickUnusedPort() int {
    37  	port, err := freeport.GetFreePort()
    38  	if err != nil {
    39  		panic(err)
    40  	}
    41  	return port
    42  }
    43  
    44  type webdriverTest func(t *testing.T, app AppServer, wd selenium.WebDriver)
    45  
    46  // Generic helpers for WebDriver
    47  
    48  // GetWebDriver starts a WebDriver service (server) and creates a remote
    49  // (client).
    50  // Note: Make sure to close the remote first and the service later, e.g.
    51  //
    52  // server, driver, err := GetWebDriver()
    53  // if err != nil {
    54  //   panic(err)
    55  // }
    56  // defer server.Stop()
    57  // defer driver.Quit()
    58  func GetWebDriver() (*selenium.Service, selenium.WebDriver, error) {
    59  	var options []selenium.ServiceOption
    60  	if *startFrameBuffer {
    61  		// Use a 24-bit display to avoid https://github.com/web-platform-tests/wpt.fyi/issues/1788.
    62  		options = append(options, selenium.StartFrameBufferWithOptions(
    63  			selenium.FrameBufferOptions{ScreenSize: "1280x1024x24"}))
    64  	}
    65  	if *debug {
    66  		selenium.SetDebug(true)
    67  		options = append(options, selenium.Output(os.Stderr))
    68  	} else {
    69  		options = append(options, selenium.Output(ioutil.Discard))
    70  	}
    71  
    72  	port := pickUnusedPort()
    73  	switch *browser {
    74  	case "firefox":
    75  		return FirefoxWebDriver(port, options)
    76  	case "chrome":
    77  		return ChromeWebDriver(port, options)
    78  	}
    79  	panic("Invalid -browser value specified")
    80  }
    81  
    82  // FindShadowElements finds the shadow DOM children via the given query
    83  // selectors, recursively. The function takes a variable number of selectors;
    84  // the selectors are combined together similar to CSS descendant combinators.
    85  // However, all but the the last selector are expected to match to hosts of
    86  // shadow DOM, and the shadow DOM boundaries will be crossed.
    87  //
    88  // e.g. FindShadowElements(wd, node, "bar", "baz blah"). All matches of "bar"
    89  // must have shadow roots, and the function finds all "baz blah" within each
    90  // shadow DOM.
    91  func FindShadowElements(
    92  	d selenium.WebDriver,
    93  	e selenium.WebElement,
    94  	selectors ...string) ([]selenium.WebElement, error) {
    95  	elements := []selenium.WebElement{e}
    96  	for _, selector := range selectors {
    97  		interfaces := make([]interface{}, len(elements))
    98  		for i, e := range elements {
    99  			interfaces[i] = e
   100  		}
   101  		result, err := d.ExecuteScriptRaw(
   102  			fmt.Sprintf(`return Array.from(arguments)
   103  				.reduce((s, e) => {
   104  					return e.shadowRoot ? s.concat(Array.from(e.shadowRoot.querySelectorAll('%s'))) : s
   105  				}, [])`,
   106  				selector),
   107  			interfaces)
   108  		if err != nil {
   109  			panic(err.Error())
   110  		}
   111  		elements, err = d.DecodeElements(result)
   112  		if err != nil {
   113  			return nil, err
   114  		}
   115  	}
   116  	return elements, nil
   117  }
   118  
   119  // FindShadowElement returns the first element found by an equivalent call to
   120  // FindShadowElements.
   121  func FindShadowElement(
   122  	d selenium.WebDriver,
   123  	e selenium.WebElement,
   124  	selectors ...string) (selenium.WebElement, error) {
   125  	elements, err := FindShadowElements(d, e, selectors...)
   126  	if err != nil || len(elements) < 1 {
   127  		return nil, err
   128  	}
   129  	return elements[0], nil
   130  }
   131  
   132  // FindShadowText returns the Text of the element returned by an equivalent
   133  // call to FindShadowElement.
   134  func FindShadowText(
   135  	d selenium.WebDriver,
   136  	e selenium.WebElement,
   137  	selectors ...string) (string, error) {
   138  	element, err := FindShadowElement(d, e, selectors...)
   139  	if err != nil {
   140  		return "", err
   141  	}
   142  	return element.Text()
   143  }
   144  
   145  // ExtractScriptRawValue extracts the value of a given key from the return
   146  // value of webdriver.ExecuteScriptRaw (raw bytes).
   147  func ExtractScriptRawValue(bytes []byte, key string) (value interface{}, err error) {
   148  	var parsed map[string]interface{}
   149  	if err = json.Unmarshal(bytes, &parsed); err != nil {
   150  		return nil, err
   151  	}
   152  	return parsed[key], nil
   153  }
   154  
   155  // The following are helpers specific to wpt.fyi.
   156  
   157  // runWebdriverTest is a helper for starting both the server and WebDriver for a test.
   158  func runWebdriverTest(t *testing.T, test webdriverTest) {
   159  	app, err := NewWebserver()
   160  	if err != nil {
   161  		log.Println("Failed to create webserver: " + err.Error())
   162  		panic(err)
   163  	}
   164  	defer app.Close()
   165  
   166  	service, wd, err := GetWebDriver()
   167  	if err != nil {
   168  		log.Println("Failed to create webdriver: " + err.Error())
   169  		panic(err)
   170  	}
   171  	defer service.Stop()
   172  	defer wd.Quit()
   173  
   174  	test(t, app, wd)
   175  }
   176  
   177  func getTestRunElements(wd selenium.WebDriver, element string) ([]selenium.WebElement, error) {
   178  	e, err := wd.FindElement(selenium.ByCSSSelector, "wpt-app")
   179  	if err != nil {
   180  		return nil, err
   181  	}
   182  	return FindShadowElements(wd, e, element, "test-run")
   183  }
   184  
   185  func getPathPartElements(wd selenium.WebDriver, element string) ([]selenium.WebElement, error) {
   186  	e, err := wd.FindElement(selenium.ByTagName, "wpt-app")
   187  	if err != nil {
   188  		return nil, err
   189  	}
   190  	return FindShadowElements(wd, e, element, "path-part")
   191  }