github.com/nya3jp/tast@v0.0.0-20230601000426-85c8e4d83a9b/docs/howto.md (about)

     1  # Tast How-To: (go/tast-howto)
     2  
     3  > This document assumes that you've already gone through [Codelab #1].
     4  
     5  This document is intended to give an overview of some of the possibilities you have for the set up and evaluation of your Tast test. This is not an exhaustive list but contains some of the most used techniques that are available to create tests.
     6  
     7  [Codelab #1]: codelab_1.md
     8  
     9  ## Evaluation
    10  ### Checking wrapped errors
    11  Go offers the functionality to wrap errors in other errors to allow returning all occurred error messages from a function call. To check if any of these wrapped errors is of a specific type that should be handled differently [errors.Is] can be used.
    12  ```
    13  var ErrWindowNotFound = errors.New("window not found")
    14  // FindMinimizedWindow returns a minimized window, if any. If there is no minimized
    15  // window, ErrWindowNotFound is returned.
    16  func FindMinimizedWindow() (*Window, error) {
    17  	ws, err := findAllWindows()
    18  	if err != nil {
    19  		return nil, err
    20  	}
    21  	for _, w := range ws {
    22  		if w.Minimized {
    23  			return w, nil
    24  		}
    25  	}
    26  	return nil, ErrWindowNotFound
    27  }
    28  
    29  func someFunction(...) error {
    30  	w, err := FindMinimizedWindow()
    31  	if err != nil {
    32  		if errors.Is(err, ErrWindowNotFound) {
    33  			return nil
    34  		}
    35  		return err
    36  	}
    37  	...
    38  }
    39  ```
    40  
    41  [errors.Is]: https://godoc.org/chromium.googlesource.com/chromiumos/platform/tast.git/src/go.chromium.org/tast/core/errors#Is
    42  
    43  ### Command line
    44  As ChromeOS is based on Linux we can execute Linux commands on the command line that can give us the needed information of the state of the system. This is done with the [testexec.CommandContext] function.
    45  The `CommandContext()` function wraps the standard go exec package to honor the timeout of the context in which the test is running.
    46  ```
    47  out, err := testexec.CommandContext(ctx, "lshw", "-C", "multimedia").Output(testexec.DumpLogOnError)
    48  if err != nil {
    49  	// Do error handling here.
    50  }
    51  ```
    52  In the example the command `lshw -C multimedia` is executed on the command line. The output of the execution is written into the out variable by calling the `Output` function and should then contain a list of all connected multimedia devices.
    53  By passing `testexec.DumpLogOnError` we also get the stderr output in case the execution fails.
    54  
    55  [testexec.CommandContext]: https://godoc.org/chromium.googlesource.com/chromiumos/platform/tast-tests.git/src/go.chromium.org/tast-tests/cros/common/testexec#CommandContext
    56  
    57  ### Checking windows
    58  In some cases checking if certain windows have been opened, or a certain number of windows have been opened can be enough to check if a test was successful or not. To do that [ash.GetAllWindows] can be used. See the [ash package documentation] for more information.
    59  It requires a context and a test connection, which is obtained by a call to [chrome.TestAPIConn], and will return an array of all open windows. This array can then be checked for example for the title of the windows to see if a desired window is open.
    60  ```
    61  tconn, err := cr.TestAPIConn(ctx)
    62  if err != nil {
    63  	// Do error handling here.
    64  }
    65  
    66  ws, err := ash.GetAllWindows(ctx, tconn)
    67  if err != nil {
    68  	// Do error handling here.
    69  }
    70  
    71  // Find the desired window with expectedTitle.
    72  for _, w := range ws {
    73  	if strings.Contains(w.Title, expectedTitle) {
    74  		// The test was successful.
    75  	}
    76  }
    77  ```
    78  
    79  [ash.GetAllWindows]: https://godoc.org/chromium.googlesource.com/chromiumos/platform/tast-tests.git/src/go.chromium.org/tast-tests/cros/local/chrome/ash#GetAllWindows
    80  [chrome.TestAPIConn]: https://godoc.org/chromium.googlesource.com/chromiumos/platform/tast-tests.git/src/go.chromium.org/tast-tests/cros/local/chrome#Chrome.TestAPIConn
    81  [ash package documentation]: https://chromium.googlesource.com/chromium/src/+/HEAD/ash/README.md
    82  
    83  ### Checking files
    84  Similar to the windows the existence or non exsitence of a file might be the needed information to determine if the test was successful.
    85  Go offers the [os.Stat] function for checking the existence or attributes of files.
    86  ```
    87  fileInfo, err := os.Stat("/path/to/file/myfile")
    88  if os.IsNotExist(err) {
    89  	return ...  // File was not found
    90  }
    91  if err != nil {
    92  	return ...  // Unknown error occurred
    93  }
    94  // File exists, fileInfo is valid
    95  ```
    96  If [os.Stat] doesn't return an error, the file exists and additional information about the file is written to `fileInfo`.
    97  
    98  [os.Stat]: https://golang.org/pkg/os/#Stat
    99  
   100  ### JavaScript evaluation
   101  With [chrome.Conn.Eval] arbitrary JavaScript expressions can be evaluated. The function takes a context, a JavaScript expression and an interface as arguments. If the JavaScript expression returns a value, it will be unmarshaled into the given interface parameter. If umarshalling fails an error will be returned.
   102  
   103  ```
   104  conn, err := cr.NewConn(ctx, URL)
   105  if err != nil {
   106  	// Do error handling here.
   107  }
   108  defer conn.Close()
   109  
   110  var message string
   111  if err := conn.Eval(ctx, `document.getElementById('element_id').innerText`, &message); err != nil {
   112  	// Do error handling here.
   113  }
   114  if strings.Contains(message, 'this is the element_id') {
   115  	// The test was successful.
   116  }
   117  ```
   118  In this example we open a new Chrome window with some URL and then we evaluate the JavaScript expression `document.getElementById('element_id').innerText` in this Chrome window. The result is written into the string message and is then checked if it contains a desired text.
   119  This can also be used for expressions returning a promise, in which case the function will wait until the promise is settled.
   120  ```
   121  const code = `return new Promise((resolve, reject) => {
   122  	const element = document.getElementById('element_id');
   123  	if (element === null) {
   124  		resolve(false);
   125  		return;
   126  	}
   127  	if (element.innerText !== 'some text') {
   128  		reject(new Error('Unexpected inner text: want some text; got ' + element.innerText));
   129  		return;
   130  	}
   131  	resolve(true);
   132  })`
   133  
   134  var found bool
   135  if err := conn.Eval(ctx, code, &found); err != nil {
   136  	// Do error handling here.
   137  }
   138  ```
   139  The [chrome.Conn.Eval] can also be used on the connection to Tast's test extension which gives access to other APIs. A connection can be created with [chrome.TestAPIConn]. The connection to Tast's test extension should not be closed as it is shared.
   140  
   141  [chrome.Conn.Eval]: https://godoc.org/chromium.googlesource.com/chromiumos/platform/tast-tests.git/src/go.chromium.org/tast-tests/cros/local/chrome#Conn.Eval
   142  
   143  ### Find the JavaScript path for an element
   144  In the previous paragraph we took a look at how to evaluate JavaScript expressions in a Tast test, however finding the JavaScript expression you need for a certain test can be difficult.
   145  The Developer Tools of Chrome can be very helpful for that. Open them by pressing CTRL + SHIFT +  I (or by opening the menu -> more Tools -> Developer Tools) in a Chrome window. In the Elements tab you can browse through the elements of a page and expand them. The selected element will be highlighted. Once you got to the element you want to check right click it in the Elements tab and select Copy -> Copy JS path. This gives you the expression to get the desired element. In the Console Tab you can try beforehand if the JavaScript expression you want to use delivers the desired output.
   146  
   147  ### Interacting with the UI
   148  It is also possible to directly interact with the elements of the UI (like clicking on them, or just hovering over the mouse), or just to get information about their status. This can be done through the Test API with the help of the automation library. The basics of the usage of this library can be found in [Codelab #3].
   149  
   150  [Codelab #3]: codelab_3.md
   151  
   152  ### Waiting in tests
   153  To check some condition it is sometimes necessary to wait until certain changes have been processed in ChromeOS. For such cases the [testing.Poll] function should be used instead of sleeping in tests, as it does not introduce unnecessary delays and race conditions in integration tests. See also [Context and timeouts].
   154  ```
   155  // Wait until the condition is true.
   156  if err := testing.Poll(ctx, func(ctx context.Context) error {
   157  
   158  	if err := doSomething(); err != nil {
   159  
   160  		// In case something went wrong we can stop waiting and return an error with testing.PollBreak().
   161  		return testing.PollBreak(errors.Wrap(err, "failed to do something critical"))
   162  	}
   163  
   164  	// Get the current state of our condition.
   165  	condition, err := checkCondition()
   166  	if err != nil {
   167  		// Do error handling here.
   168  	}
   169  	if condition != expectedCondition {
   170  		return errors.Errorf("unexpected condition: got %q; want %q", condition, expectedCondition)
   171  	}
   172  
   173  	return nil
   174  
   175  }, &testing.PollOptions{
   176  	Timeout: 30 * time.Second,
   177  	Interval: 5 * time.Second,
   178  }); err != nil {
   179  	s.Fatal("Did not reach expected state: ", err)
   180  }
   181  ```
   182  
   183  [testing.Poll]: https://godoc.org/chromium.googlesource.com/chromiumos/platform/tast.git/src/go.chromium.org/tast/core/testing#Poll
   184  [Context and timeouts]: https://chromium.googlesource.com/chromiumos/platform/tast/+/refs/heads/main/docs/writing_tests.md#contexts-and-timeouts
   185  
   186  ## Setup
   187  ### Using the Launcher
   188  We can use the Chrome Launcher in our tests to search and start applications or websites.
   189  ```
   190  tconn, err := cr.TestAPIConn(ctx)
   191  if err != nil {
   192  	// Do error handling here.
   193  }
   194  
   195  if err := launcher.SearchAndLaunch(ctx, tconn, appName); err != nil {
   196  	// Do error handling here.
   197  }
   198  ```
   199  The launcher requires a context, a test connection and a string that will be typed into the launcher. This example will start the application defined in `appName`, or if it isn't found a Google search for `appName` will be opened in a new Chrome window.
   200  
   201  ### HTTP Server
   202  In many tests having a local http server can be helpful to avoid being dependent on a network connection. The [httptest.NewServer] function can be used to start your own http server from within the test.
   203  ```
   204  func init() {
   205  	testing.AddTest(&testing.Test{
   206  		Func: MyTest,
   207  		Data:         []string{"my_test.html", "my_test.js"},
   208  	})
   209  }
   210  
   211  func MyTest(ctx context.Context, s *testing.State) {
   212  
   213  	server := httptest.NewServer(http.FileServer(s.DataFileSystem()))
   214  	defer server.Close()
   215  
   216  	conn, err := cr.NewConn(ctx, server.URL+"/my_test.html")
   217  	if err != nil {
   218  		// Do error handling here.
   219  	}
   220  	defer conn.Close()
   221  	...
   222  }
   223  ```
   224  In this example we created a small website with two files: `my_test.html` and `my_test.js`, and added the files to the test in the definition of the metadata for the test.
   225  In the test we start a HTTP server as a `http.FileServer` which serves requests for the files located in the folder given as argument. The used folder, `s.DataFileSystem()`, is the folder where additional files for the test are copied to on the test device, which is where our files for the website end up. Then we open the website in a new Chrome window.
   226  
   227  [httptest.NewServer]: https://golang.org/pkg/os/