blob: 54d19aaec85e7ffd98eb6fbb6eebebe65ef9cd6c [file] [view]
# Tast: Testing Isolated Web Applications (IWAs) on ChromeOS (go/tast-iwa)
> This document assumes that you are familiar with Tast [test writing], [execution], and [debugging], and have already gone through [Codelab #1] and [Codelab #2]. You should also know what [Isolated Web Application] is.
[Codelab #1]: codelab_1.md
[Codelab #2]: codelab_2.md
[test writing]: http://go/tast-writing
[execution]: http://go/tast-running
[debugging]: http://go/debug-tast-tests
[Isolated Web Application]: https://chromeos.dev/en/tutorials/getting-started-with-isolated-web-apps
[TOC]
# Testing Isolated Web Apps in Tast
This document provides guidance on how to test Isolated Web Apps (IWAs) within the Tast testing framework.
## Testing Pyramid and Lower-Level Testing
* Adopt a **testing pyramid** approach, which emphasizes a balance of different test types. Read more about it at [Google Testing Blog](https://testing.googleblog.com/2024/10/smurf-beyond-test-pyramid.html).
* There should be a significant number of tests checking the application's code, focusing on individual components and their interactions, including components' integrations. These tests should be prioritized to ensure a robust and reliable application.
* Testing the integration of the IWA with the Chrome browser is currently under development (crbug.com/337872319).
## Setting up the Test Environment
1. **Test Device:**
* **Recommended Approach: VM Testing**
* It is highly recommended to start testing with a ChromeOS VM. This allows for faster iteration, easier debugging, and a more controlled environment.
* **VM Setup:**
1. **Linux Chromium Checkout:** Ensure you have a [Linux Chromium checkout](https://chromium.googlesource.com/chromium/src/+/HEAD/docs/linux/build_instructions.md) with `depot_tools` installed.
2. **Virtualization Enabled:** Your system firmware (BIOS) must have virtualization features enabled, and KVM must be enabled in your kernel.
3. **Simple Chrome Setup:** You should have [Simple Chrome](https://www.chromium.org/chromium-os/developer-library/guides/development/simple-chrome-workflow/) set up.
4. **Choose a Board:** Select a suitable board. `betty` is recommended for Googlers, while `amd64-generic-vm` is suitable for open-source contributors. Set the board using `export BOARD=betty`
5. **Launch the VM:** Use `cros vm start` or `cros_vm --board ${BOARD}` to launch the VM.\
Refer to the [official ChromeOS VM documentation](https://www.chromium.org/chromium-os/developer-library/guides/containers/cros-vm/) for detailed instructions.
* **Why VM First?** VMs offer a faster, more controlled environment for initial testing and development. They allow for quick modifications and easier debugging before moving to physical hardware.
* **Physical Device Testing:**
* While VMs are ideal for initial testing, it's crucial to test on physical devices.
* **Recommended Devices:**
* **Primary Device:** Target the most used device within your customer base. This ensures the IWA functions well on the most common hardware.
* **Low-End Device:** Include a low-end device in your testing to ensure the IWA remains performant even under resource constraints.
* **Why Physical Devices?** Physical devices provide a real-world view of performance, hardware interactions, and user experience.
1. **Update manifest:** The application should be deployed with its update manifest accessible.
## Steps to Writing Tast Tests for IWAs
Here's a general outline for writing Tast tests for IWAs:
### Define application details
```go
const (
kitchenSinkIWAUpdateManifestURL = "https://github.com/chromeos/iwa-sink/releases/latest/download/update.json"
kitchenSinkIWAWebBundleID = "aiv4bxauvcu3zvbu6r5yynoh4atkzqqaoeof5mwz54b4zfywcrjuoaacai"
)
```
### Prepare the policies
```go
pb := policy.NewBlob()
policies := []policy.Policy{
&policy.IsolatedWebAppInstallForceList{
Val: []*policy.IsolatedWebAppInstallForceListValue{
{
UpdateManifestUrl: kitchenSinkIWAUpdateManifestURL,
WebBundleId: kitchenSinkIWAWebBundleID,
PinnedVersion: "0.17.0",
},
},
},
}
```
**NOTE:**
It is recommended to use `PinnedVersion` and the latest ChromeOS to ensure that ChromeOS changes do not impact the IWA's functionality. When testing an unpinned IWA version, use a stable, unchanging ChromeOS version. This will help isolate whether failures are due to changes in the application or in ChromeOS.
### Add policies and load them
```go
if err := pb.AddPolicies(policies); err != nil {
s.Fatal("Failed to add policies for public account setup: ", err)
}
if err := policyutil.ServeBlobAndRefresh(ctx, fdms, cr, pb); err != nil {
s.Fatal("Failed to update policies: ", err)
}
```
### Create a test connection
```go
tconn, err := cr.TestAPIConn(ctx)
if err != nil {
s.Fatal("Failed to create test API connection: ", err)
}
```
### Create uiauto object
```go
ui := uiauto.New(tconn)
createSocketConnButton := nodewith.Name("Create new socket connection").Role(role.Button)
sendMessageTextField := nodewith.Name("Send a message").Role(role.TextField)
engageMessage := nodewith.Name("Foo").Role(role.InlineTextBox)
responseMessage := nodewith.Name("Bar").Role(role.InlineTextBox)
```
### Create virtual keyboard object
```go
kb, err := input.VirtualKeyboard(ctx)
if err != nil {
s.Fatal("Failed to get keyboard: ", err)
}
defer kb.Close(ctx)
```
### Start the application
```go
if err := uiauto.Combine("Launch Kitchen Sink IWA",
// Launch Kitchen Sink IWA.
launcher.SearchAndLaunch(tconn, kb, "Kitchen Sink IWA"),
// Wait till the IWA is launched and the Create Socket button appears.
ui.WithTimeout(30*time.Second).WaitUntilExists(createSocketConnButton),
)(ctx); err != nil {
s.Fatal("Failed to launch Kitchen Sink IWA: ", err)
}
```
### Interact with the application
```go
if err := uiauto.Combine("Interact with Kitchen Sink IWA UI",
// Create a new socket connection.
ui.LeftClick(createSocketConnButton),
ui.WaitUntilExists(sendMessageTextField.Nth(1)),
// Send messages to the TCP Server.
ui.LeftClickUntil(sendMessageTextField.First(), ui.Exists(sendMessageTextField.Focused())),
kb.TypeAction("Foo\n"),
ui.WaitUntilExists(engageMessage),
// Send a message from the TCP Server.
ui.LeftClickUntil(sendMessageTextField.Nth(1), ui.Exists(sendMessageTextField.Focused())),
kb.TypeAction("Bar\n"),
ui.WaitUntilExists(responseMessage),
)(ctx); err != nil {
s.Fatal("Failed to interact with the Kitchen Sink IWA: ", err)
}
```
The full code of the example is available in the [LaunchIWA](https://source.chromium.org/chromiumos/chromiumos/codesearch/+/main:src/platform/tast-tests/src/go.chromium.org/tast-tests/cros/local/bundles/cros/iwa/launch_iwa.go) test.
## Important Considerations
* **Test Stability:** Write robust tests that can handle potential network issues, UI changes, and other factors that might impact the IWA's behavior.
* **Error Handling:** Include proper error handling to identify and address issues quickly.
* **Version Control:** Test against different versions of the IWA to ensure compatibility.
* **Clear Test Descriptions:** Provide descriptive test names and comments to make the tests easier to understand.
* **Follow [design principles]** to make your test more robust.
* Prioritize **VM testing** for initial development and debugging.
* Include testing on the most used and a low-end physical device for optimal coverage.
### Commercial setup considerations
* If a Chrome restart is required (e.g., for auto-starting the IWA), use `fixture.FakeDMS`. Chrome restart needs depend on the type of policies you are trying to apply. For example, the `MultiScreenCaptureAllowedForUrls` policy requires a restart (`Dynamic Policy Refresh: No`).
* To launch and manually interact with the IWA, use `fixture.ChromePolicyLoggedIn`.
[design principles]: http://go/tast-design
[MultiScreenCaptureAllowedForUrls]: https://chromeenterprise.google/policies/#MultiScreenCaptureAllowedForUrls
### Examples
All IWA tests are held within the [iwa package](https://source.chromium.org/chromiumos/chromiumos/codesearch/+/main:src/platform/tast-tests/src/go.chromium.org/tast-tests/cros/local/bundles/cros/iwa/). Examples include:
* [Screen capture test](https://source.chromium.org/chromiumos/chromiumos/codesearch/+/main:src/platform/tast-tests/src/go.chromium.org/tast-tests/cros/local/bundles/cros/iwa/screen_capture.go).
* [Starting IWA app from launcher test](https://source.chromium.org/chromiumos/chromiumos/codesearch/+/main:src/platform/tast-tests/src/go.chromium.org/tast-tests/cros/local/bundles/cros/iwa/launch_iwa.go).
* [Autostart and prevent closing test](https://source.chromium.org/chromiumos/chromiumos/codesearch/+/main:src/platform/tast-tests/src/go.chromium.org/tast-tests/cros/local/bundles/cros/iwa/autostart_iwa.go).
## Passing Variables to the Test
This is useful when you need to pass sensitive information, such as server credentials, without hardcoding them into the test itself.
### Using Run Command
During runtime you can pass variables to a Tast test using the `-var` flag with the `tast run` command. This may apply to you if you want to connect to a server and login, and not store those secrets in the test code.
To store and access private data such as credentials:
**Syntax**:
```bash
tast run -var=name=value <dut> <tests>
```
* `name`: The name of the variable.
* `value`: The value to assign to the variable.
* `<dut>`: The target device.
* `<tests>`: The test(s) to run.
Multiple variables can be passed by repeating the `-var` flag.
**Example**:
```bash
tast run -var=serverUrl=https://validTestEndpoint -var=userName=foo -var=userPassword=bar <dut> <tests>
```
All details are available on the following [runtime variables](https://chromium.googlesource.com/chromiumos/platform/tast/+/b5d9cbe7de67/docs/writing_tests.md#runtime-variables) documentation.
In your Tast test, you can access the variable using the `s.Var` method. Make sure to declare the variable in the `Vars` field of the test's struct.
**Example**:
```go
package mytestpackage
var exampleStrVar = testing.RegisterVarString(
"mytestpackage.ServerUrl",
"Default value",
"An example variable of string type",
)
func init() {
testing.AddTest(&testing.Test{
Func: MyTest,
Desc: "Test that will read the variable from the command line argument",
// ...
// ...
})
}
func MyTest(ctx context.Context, s *testing.State) {
strVal := exampleStrVar.Value()
// ...
}
```
A full code example of this is in [this](https://chromium.googlesource.com/chromiumos/platform/tast-tests/+/HEAD/src/go.chromium.org/tast-tests/cros/local/bundles/cros/example/runtime_vars.go) test.
### Using Secret Variable
This way can be used by an internal developer, who has access to the `tast-tests-private` package. Learn more about it from [this](https://chromium.googlesource.com/chromiumos/platform/tast/+/HEAD/docs/writing_tests.md#secret-variables) article.
# Testing IWAs in Kiosk Mode
The main difference between a regular IWA test and a Kiosk IWA test lies in the setup. Instead of using policies to force-install the IWA, you will configure the device to launch directly into the IWA in a Kiosk session.
## Steps to Writing Tast Tests for IWAs in Kiosk Mode
Here’s a general outline for writing Tast tests for IWAs in Kiosk mode:
1. **Use the `fixture.FakeDMSEnrolled` fixture** in your test definition to simulate a managed device.
```go
func init() {
testing.AddTest(&testing.Test{
// ...
Fixture: fixture.FakeDMSEnrolled,
})
}
```
2. **Define the application details**, including the update manifest URL and the web bundle ID for your IWA.
```go
var updateManifestURL string = "https://github.com/chromeos/iwa-sink/releases/latest/download/update.json"
var webBundleID string = "aiv4bxauvcu3zvbu6r5yynoh4atkzqqaoeof5mwz54b4zfywcrjuoaacai"
```
3. **Start Chrome in Kiosk mode** using `kioskmode.New()`. You will need to define a `DeviceLocalAccount` for the IWA and configure it to auto-launch.
```go
iwaKioskAccountType := policy.AccountTypeKioskIWA
kiosk, cr, err := kioskmode.New(
ctx,
fdms,
s.RequiredVar("ui.signinProfileTestExtensionManifestKey"),
kioskmode.CustomLocalAccounts(
&policy.DeviceLocalAccounts{
Val: []policy.DeviceLocalAccountInfo{
{
AccountID: &kioskmode.KioskAppAccountID,
AccountType: &iwaKioskAccountType,
IsolatedWebAppKioskInfo: &policy.IsolatedWebAppKioskInfo{
WebBundleId: &webBundleID,
ManifestUrl: &updateManifestURL,
},
},
},
},
),
kioskmode.AutoLaunch(kioskmode.KioskAppAccountID),
)
if err != nil {
s.Fatal("Failed to start Chrome in Kiosk mode: ", err)
}
defer kiosk.Close(cleanupContext)
```
4. **Wait for the Kiosk app to launch** using `kiosk.WaitLaunchLogs()`.
```go
if err := kiosk.WaitLaunchLogs(ctx); err != nil {
s.Fatal("Failed to launch Kiosk: ", err)
}
```
5. **Interact with the application** using the `uiauto` library, just as you would in a regular IWA test. You can create a test API connection, define UI nodes, and perform actions like clicking buttons and typing text.
```go
tconn, err := cr.TestAPIConn(ctx)
if err != nil {
s.Fatal("Failed to create test API connection: ", err)
}
ui := uiauto.New(tconn)
createSocketConnButton := nodewith.Name("Create new socket connection").Role(role.Button)
// ...
if err := uiauto.Combine("Interact with Kitchen Sink IWA UI",
ui.WithTimeout(30*time.Second).WaitUntilExists(createSocketConnButton),
// ...
)(ctx); err != nil {
s.Fatal("Failed to interact with the Kitchen Sink IWA: ", err)
}
```
## Example
For a complete example of an IWA test running in Kiosk mode, see the [launch_iwa.go](https://source.chromium.org/chromiumos/chromiumos/codesearch/+/main:src/platform/tast-tests/src/go.chromium.org/tast-tests/cros/local/bundles/cros/kiosk/launch_iwa.go) test.