blob: 916f7e63f8c0d09ffe067301e5d82d76aa633f92 [file] [log] [blame]
// Copyright 2023 The ChromiumOS Authors
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
package wifi
import (
"context"
"net"
"time"
"go.chromium.org/tast-tests/cros/common/tbdep"
"google.golang.org/protobuf/types/known/emptypb"
"go.chromium.org/tast-tests/cros/remote/bundles/cros/wifi/wifiutil"
"go.chromium.org/tast-tests/cros/remote/wificell"
"go.chromium.org/tast-tests/cros/services/cros/chrome/uiauto/ossettings"
"go.chromium.org/tast-tests/cros/services/cros/ui"
"go.chromium.org/tast/core/ctxutil"
"go.chromium.org/tast/core/errors"
"go.chromium.org/tast/core/testing"
)
func init() {
testing.AddTest(&testing.Test{
Func: IPSettingsView,
LacrosStatus: testing.LacrosVariantUnneeded,
LifeCycleStage: testing.LifeCycleOwnerMonitored,
Desc: "Verify that user is able to view the current ip/gateway/subnet mask/MAC address of the device",
Contacts: []string{
"cros-connectivity@google.com",
"chromeos-connectivity-engprod@google.com",
"shijinabraham@google.com",
"chadduffin@chromium.org",
},
BugComponent: "b:1131912", // ChromeOS > Software > System Services > Connectivity > WiFi
Attr: []string{"group:wificell", "wificell_e2e"},
TestBedDeps: []string{tbdep.Wificell, tbdep.WifiStateNormal, tbdep.BluetoothStateNormal, tbdep.PeripheralWifiStateWorking},
ServiceDeps: []string{
wificell.ShillServiceName,
wifiutil.FaillogServiceName,
"tast.cros.ui.AutomationService",
"tast.cros.chrome.uiauto.ossettings.OsSettingsService",
"tast.cros.browser.ChromeService",
},
SoftwareDeps: []string{"chrome"},
Fixture: wificell.FixtureID(wificell.TFFeaturesNone),
})
}
// IPSettingsView verifies that user is able to view the current ip/gateway/subnet mask/MAC address of the device.
func IPSettingsView(ctx context.Context, s *testing.State) {
tf := s.FixtValue().(*wificell.TestFixture)
ap, err := tf.DefaultOpenNetworkAP(ctx)
if err != nil {
s.Fatal("Failed to configure AP: ", err)
}
defer func(ctx context.Context) {
if err := tf.DeconfigAP(ctx, ap); err != nil {
s.Error("Failed to deconfig AP: ", err)
}
}(ctx)
ctx, cancel := tf.ReserveForDeconfigAP(ctx, ap)
defer cancel()
cleanupCtx := ctx
ctx, cancel = ctxutil.Shorten(ctx, 10*time.Second)
defer cancel()
rpcClient := tf.DUTRPC(wificell.DefaultDUT)
cr := ui.NewChromeServiceClient(rpcClient.Conn)
if _, err := cr.New(ctx, &ui.NewRequest{}); err != nil {
s.Fatal("Failed to start Chrome: ", err)
}
defer cr.Close(cleanupCtx, &emptypb.Empty{})
if _, err = tf.ConnectWifiAPFromDUT(ctx, wificell.DefaultDUT, ap); err != nil {
s.Fatal("Failed to connect to WiFi: ", err)
}
defer tf.CleanDisconnectDUTFromWifi(ctx, wificell.DefaultDUT)
ctx, cancel = tf.ReserveForDisconnect(ctx)
defer cancel()
settingsSvc := ossettings.NewOsSettingsServiceClient(rpcClient.Conn)
if _, err := settingsSvc.OpenNetworkDetailPage(ctx, &ossettings.OpenNetworkDetailPageRequest{
NetworkName: ap.Config().SSID,
NetworkType: ossettings.OpenNetworkDetailPageRequest_WIFI,
}); err != nil {
s.Fatal("Failed to open network page: ", err)
}
defer settingsSvc.Close(cleanupCtx, &emptypb.Empty{})
defer wifiutil.DumpUITreeWithScreenshotToFile(cleanupCtx, rpcClient.Conn, s.HasError, "ui_dump")
// The root node helper of the settings window.
var settingsNodeHelper = ui.Node().Ancestor(ui.Node().NameRegex(`^Settings`).Role(ui.Role_ROLE_WINDOW).HasClass("BrowserFrame").Finder())
// Open the IP settings view.
uiSvc := ui.NewAutomationServiceClient(rpcClient.Conn)
if _, err := uiSvc.LeftClick(ctx, &ui.LeftClickRequest{
Finder: settingsNodeHelper.Name("Show network address settings").Role(ui.Role_ROLE_BUTTON).Finder(),
}); err != nil {
s.Fatal("Failed open the IP settings view: ", err)
}
// TODO(b/263311524): Update this test to check that the routing prefix (subnet mask),
// gateway, and MAC address match the values reported by Shill.
if err := checkIPSettings(ctx, tf, uiSvc, settingsNodeHelper); err != nil {
s.Fatal("Failed to check the basic IP settings: ", err)
}
}
func checkIPSettings(ctx context.Context, tf *wificell.TestFixture, uiSvc ui.AutomationServiceClient, root *ui.NodeHelper) error {
for _, section := range []struct {
name string
contentVerifier func(ctx context.Context, tf *wificell.TestFixture, uiSvc ui.AutomationServiceClient, root *ui.NodeHelper) error
}{
{"IP address", checkIPv4Content(1 /* order */)},
{"Subnet mask", checkIPContent(2 /* order */)},
{"Gateway", checkIPContent(3 /* order */)},
{"MAC address", checkMACAddressContent()},
} {
if _, err := uiSvc.WaitUntilExists(ctx, &ui.WaitUntilExistsRequest{
Finder: root.Name(section.name).Role(ui.Role_ROLE_STATIC_TEXT).Finder(),
}); err != nil {
return errors.Wrapf(err, "failed to wait for section to exist: %q", section.name)
}
// Make the section is observable and can be captured by screenshot.
if _, err := uiSvc.MakeVisible(ctx, &ui.MakeVisibleRequest{
Finder: root.Name(section.name).Role(ui.Role_ROLE_STATIC_TEXT).Finder(),
}); err != nil {
return errors.Wrapf(err, "failed to make section visible: %q", section.name)
}
if err := section.contentVerifier(ctx, tf, uiSvc, root); err != nil {
return errors.Wrap(err, "failed to verify IP settings contents")
}
}
return nil
}
// fetchIPContent returns the IP address that UI provided.
// The |order| parameter is provided since all of the IP text boxes are identical on the UI tree
// and are only distinguished by their order.
func fetchIPContent(order int32) func(ctx context.Context, uiSvc ui.AutomationServiceClient, root *ui.NodeHelper) (string, error) {
return func(ctx context.Context, uiSvc ui.AutomationServiceClient, root *ui.NodeHelper) (string, error) {
// This finder uses a regex that will match any IPv4 address, e.g. 127.0.0.1, and is required
// since the network details page has very little other information to leverage when searching.
textBox := ui.Node().NameRegex(`^([0-9]{1,3}\.){3}[0-9]{1,3}$`).Role(ui.Role_ROLE_INLINE_TEXT_BOX).Nth(order).Finder()
resp, err := uiSvc.Info(ctx, &ui.InfoRequest{Finder: textBox})
if err != nil {
return "", errors.Wrap(err, "failed to fetch IP content")
}
return resp.NodeInfo.Name, nil
}
}
// checkIPContent checks a IP text content is a valid IP address.
// The |order| parameter is provided since all of the IP text boxes are identical on the UI tree
// and are only distinguished by their order.
func checkIPContent(order int32) func(ctx context.Context, tf *wificell.TestFixture, uiSvc ui.AutomationServiceClient, root *ui.NodeHelper) error {
return func(ctx context.Context, tf *wificell.TestFixture, uiSvc ui.AutomationServiceClient, root *ui.NodeHelper) error {
ip, err := fetchIPContent(order)(ctx, uiSvc, root)
if err != nil {
return err
}
// Validate the content text.
if address := net.ParseIP(ip); address == nil {
return errors.Errorf("failed to to parse content %s", ip)
}
return nil
}
}
// checkIPv4Content checks a IP text content is a valid IP address and is the same IPv4 address reported by router.
// The |order| parameter is provided since all of the IP text boxes are identical on the UI tree
// and are only distinguished by their order.
func checkIPv4Content(order int32) func(ctx context.Context, tf *wificell.TestFixture, uiSvc ui.AutomationServiceClient, root *ui.NodeHelper) error {
return func(ctx context.Context, tf *wificell.TestFixture, uiSvc ui.AutomationServiceClient, root *ui.NodeHelper) error {
ip, err := fetchIPContent(order)(ctx, uiSvc, root)
if err != nil {
return err
}
ipFromRouter, err := tf.DUTIPv4Addrs(ctx, wificell.DefaultDUT)
if err != nil {
return errors.Wrap(err, "failed to get IP address reported by router")
} else if len(ipFromRouter) == 0 {
return errors.New("invalid IP address")
}
// Validate the IP matches with the one reported by router.
if ip != ipFromRouter[0].String() {
return errors.Errorf("IP address shown in UI does not match router, found: %s, expected: %s", ip, ipFromRouter[0].String())
}
return nil
}
}
func checkMACAddressContent() func(ctx context.Context, tf *wificell.TestFixture, uiSvc ui.AutomationServiceClient, root *ui.NodeHelper) error {
return func(ctx context.Context, tf *wificell.TestFixture, uiSvc ui.AutomationServiceClient, root *ui.NodeHelper) error {
// TODO(b/264833164): Make these fields more identifiable and update this test.
// The node has no unique identifier and is hard to be located/distinguished from the UI tree.
// Finding the node directly by its text content since the MAC address has a certain pattern.
// The expression guarantees at least six groups of two hexadecimal digits; there won't be a collision with the IP address.
contentFinder := root.NameRegex(`^([0-9A-F]{2}[:-]){5}[0-9A-F]{2}`).Nth(1).Finder()
resp, err := uiSvc.Info(ctx, &ui.InfoRequest{Finder: contentFinder})
if err != nil {
return errors.Wrap(err, "failed to get node info")
}
// Validate the content text.
if _, err = net.ParseMAC(resp.NodeInfo.Name); err != nil {
return errors.Wrap(err, "failed to parse the MAC address")
}
return nil
}
}