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.
This document provides guidance on how to test Isolated Web Apps (IWAs) within the Tast testing framework.
Test Device:
Recommended Approach: VM Testing
depot_tools installed.betty is recommended for Googlers, while amd64-generic-vm is suitable for open-source contributors. Set the board using export BOARD=bettycros vm start or cros_vm --board ${BOARD} to launch the VM.Physical Device Testing:
Update manifest: The application should be deployed with its update manifest accessible.
Here's a general outline for writing Tast tests for IWAs:
const (
kitchenSinkIWAUpdateManifestURL = "https://github.com/chromeos/iwa-sink/releases/latest/download/update.json"
kitchenSinkIWAWebBundleID = "aiv4bxauvcu3zvbu6r5yynoh4atkzqqaoeof5mwz54b4zfywcrjuoaacai"
)
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.
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)
}
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)
sendMessageTextField := nodewith.Name("Send a message").Role(role.TextField)
engageMessage := nodewith.Name("Foo").Role(role.InlineTextBox)
responseMessage := nodewith.Name("Bar").Role(role.InlineTextBox)
kb, err := input.VirtualKeyboard(ctx)
if err != nil {
s.Fatal("Failed to get keyboard: ", err)
}
defer kb.Close(ctx)
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) }
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 test.
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).fixture.ChromePolicyLoggedIn.All IWA tests are held within the iwa package. Examples include:
This is useful when you need to pass sensitive information, such as server credentials, without hardcoding them into the test itself.
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:
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:
tast run -var=serverUrl=https://validTestEndpoint -var=userName=foo -var=userPassword=bar <dut> <tests>
All details are available on the following 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:
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 test.
This way can be used by an internal developer, who has access to the tast-tests-private package. Learn more about it from this article.
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.
Here’s a general outline for writing Tast tests for IWAs in Kiosk mode:
Use the fixture.FakeDMSEnrolled fixture in your test definition to simulate a managed device.
func init() { testing.AddTest(&testing.Test{ // ... Fixture: fixture.FakeDMSEnrolled, }) }
Define the application details, including the update manifest URL and the web bundle ID for your IWA.
var updateManifestURL string = "https://github.com/chromeos/iwa-sink/releases/latest/download/update.json"
var webBundleID string = "aiv4bxauvcu3zvbu6r5yynoh4atkzqqaoeof5mwz54b4zfywcrjuoaacai"
Start Chrome in Kiosk mode using kioskmode.New(). You will need to define a DeviceLocalAccount for the IWA and configure it to auto-launch.
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)
Wait for the Kiosk app to launch using kiosk.WaitLaunchLogs().
if err := kiosk.WaitLaunchLogs(ctx); err != nil {
s.Fatal("Failed to launch Kiosk: ", err)
}
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.
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) }
For a complete example of an IWA test running in Kiosk mode, see the launch_iwa.go test.