blob: be8b25508faa04908bc350f3621eaa56419330d6 [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"
"encoding/hex"
"encoding/json"
"fmt"
"strings"
"time"
"github.com/golang/protobuf/ptypes/empty"
"go.chromium.org/tast-tests/cros/common/tbdep"
"go.chromium.org/tast/core/ctxutil"
"go.chromium.org/tast/core/errors"
"go.chromium.org/tast/core/testing"
"google.golang.org/grpc"
"google.golang.org/protobuf/types/known/emptypb"
"go.chromium.org/tast-tests/cros/common/pci"
"go.chromium.org/tast-tests/cros/common/policy"
tdreq "go.chromium.org/tast-tests/cros/common/testdevicerequirements"
"go.chromium.org/tast-tests/cros/common/wifi/security"
"go.chromium.org/tast-tests/cros/common/wifi/security/wpa"
"go.chromium.org/tast-tests/cros/remote/bundles/cros/wifi/wifiutil"
"go.chromium.org/tast-tests/cros/remote/wificell"
ap "go.chromium.org/tast-tests/cros/remote/wificell/hostapd"
"go.chromium.org/tast-tests/cros/services/cros/chrome/uiauto/ossettings"
"go.chromium.org/tast-tests/cros/services/cros/chrome/uiauto/quicksettings"
"go.chromium.org/tast-tests/cros/services/cros/inputs"
ps "go.chromium.org/tast-tests/cros/services/cros/policy"
"go.chromium.org/tast-tests/cros/services/cros/ui"
"go.chromium.org/tast-tests/cros/services/cros/wifi"
)
// QuickSettings indicates that quick setting needs to be used.
const quickSettings int = 0
// OsSettings indicates that OS setting needs to be used.
const osSettings int = 1
// ExpectDialog indicates that join WiFi dialog is expected to be opened during test.
const expectDialog bool = true
const canNotJoinWiFi string = "Failed to join WiFi: "
const canNotAddNewWiFi string = "Failed to add new WiFi network: "
const scanWaitingFailure string = "failed to wait for current scan to be done"
const connectionVerificationFailure string = "failed to verify that WiFi was connected"
const ssidDataRemovalFailure string = "failed to remove entries for SSID"
const tabPressFailure string = "failed to move to next UI element with Tab"
const failedToStartChrome string = "DUT: failed to start Chrome: "
const canNotJoinWiFiPreTest string = "Failed to join WiFi even policies are not applied: "
var (
useDeviceGUID = "device_network_config"
notBlockedSSID = ap.RandomSSID("TAST_TEST_NOT_BLOCKED_")
blockedSSID = ap.RandomSSID("TAST_TEST_BLOCKED_")
blockedPreferredSSID = ap.RandomSSID("TAST_TEST_BLOCKED_PREFERRED_")
blockedSSIDHex = strings.ToUpper(hex.EncodeToString([]byte(blockedSSID)))
blockedPreferredSSIDHex = strings.ToUpper(hex.EncodeToString([]byte(blockedPreferredSSID)))
testPass = "some_password"
globalNetworkConfig = &policy.ONCGlobalNetworkConfiguration{
AllowOnlyPolicyNetworksToAutoconnect: false,
AllowOnlyPolicyNetworksToConnect: false,
BlockedHexSSIDs: []string{blockedSSIDHex, blockedPreferredSSIDHex},
}
allSSIDs = []string{notBlockedSSID, blockedSSID, blockedPreferredSSID}
openNetworkConfigurations = []*policy.ONCNetworkConfiguration{
{
GUID: useDeviceGUID,
Name: "DeviceWideNetworkConfig",
Type: "WiFi",
WiFi: &policy.ONCWifi{
AutoConnect: false,
Security: "None",
SSID: blockedPreferredSSID,
},
},
}
wpaNetworkConfigurations = []*policy.ONCNetworkConfiguration{
{
GUID: useDeviceGUID,
Name: "DeviceWideNetworkConfig",
Type: "WiFi",
WiFi: &policy.ONCWifi{
AutoConnect: false,
Security: "WPA-PSK",
SSID: blockedPreferredSSID,
Passphrase: testPass,
},
},
}
openConfigDevicePolicy = &policy.DeviceOpenNetworkConfiguration{
Val: &policy.ONC{
GlobalNetworkConfiguration: globalNetworkConfig,
NetworkConfigurations: openNetworkConfigurations,
},
}
wpaConfigDevicePolicy = &policy.DeviceOpenNetworkConfiguration{
Val: &policy.ONC{
GlobalNetworkConfiguration: globalNetworkConfig,
NetworkConfigurations: wpaNetworkConfigurations,
},
}
)
type policyBlockedWifiTestcase struct {
devicePolicy *policy.DeviceOpenNetworkConfiguration
apConfig security.ConfigFactory
wifiCred wifiutil.Credential
testUserSession bool
}
type localContext struct {
ctx context.Context
rpcClient *grpc.ClientConn
wifiSvc *wificell.WifiClient
wifiCred wifiutil.Credential
}
func init() {
testing.AddTest(&testing.Test{
Func: PolicyBlockedWifi,
LacrosStatus: testing.LacrosVariantUnneeded,
Desc: "Verifies that DUT respects rules for SSIDs blocked by device policy",
Contacts: []string{
"chromeos-commercial-networking@google.com",
"olsa@google.com",
},
BugComponent: "b:1000044", // ChromeOS > Software > Commercial (Enterprise) > Commercial Networking
SearchFlags: []*testing.StringPair{
pci.SearchFlag(&policy.DeviceOpenNetworkConfiguration{}, pci.VerifiedFunctionalityUI),
{
Key: "feature_id",
// verify that managed users can access or are blocked from accessing the appropriate networks as per the configuration.
// (COM_FOUND_CUJ5_TASK3_WF1).
Value: "screenplay-55005668-0de3-4314-accc-baef199664ed",
},
},
Attr: []string{"group:wificell", "wificell_commercial_unstable"},
TestBedDeps: []string{tbdep.Wificell, tbdep.WifiStateNormal, tbdep.BluetoothStateNormal, tbdep.PeripheralWifiStateWorking},
SoftwareDeps: []string{"chrome"},
// SigninProfileTestExtensionID is an id of the test extension which is
// allowed for signin profile (see http://crrev.com/772709 for details).
VarDeps: []string{"ui.signinProfileTestExtensionManifestKey"},
ServiceDeps: append(
wifiutil.JoinWifiServiceNames,
"tast.cros.policy.PolicyService",
"tast.cros.ui.AutomationService",
"tast.cros.wifi.ShillService",
"tast.cros.chrome.uiauto.ossettings.OsSettingsService",
"tast.cros.browser.ChromeService",
"tast.cros.wifi.WifiService",
),
Timeout: 25 * time.Minute,
Fixture: wificell.FixtureID(wificell.TFFeaturesEnroll),
Requirements: []string{tdreq.WiFiProcPassFW, tdreq.WiFiProcPassAVL, tdreq.WiFiProcPassAVLBeforeUpdates, tdreq.WiFiProcPassMatfunc, tdreq.WiFiProcPassMatfuncBeforeUpdates},
VariantCategory: `{"name": "WifiBtChipset_Soc_Kernel"}`,
Params: []testing.Param{
{
// TODO(b/278192058): Extend test with EAP-TLS and WPA-EAP WiFi if they have different behavior when blocked.
// Verify that DUT can connect to an open AP on login screen.
Name: "open_wifi_login_screen",
Val: policyBlockedWifiTestcase{
apConfig: nil,
wifiCred: &wifiutil.None{},
devicePolicy: openConfigDevicePolicy,
testUserSession: false,
},
},
{
// Verify that DUT can connect to an open AP inside user's session.
Name: "open_wifi_user_session",
Val: policyBlockedWifiTestcase{
apConfig: nil,
wifiCred: &wifiutil.None{},
devicePolicy: openConfigDevicePolicy,
testUserSession: true,
},
},
{
// Verify that DUT can connect to a WPA-PSK AP on login screen.
Name: "wpa_psk_wifi_login_screen",
Val: policyBlockedWifiTestcase{
apConfig: wpa.NewConfigFactory(
testPass,
wpa.Mode(wpa.ModePureWPA2),
wpa.Ciphers2(wpa.CipherCCMP),
),
wifiCred: &wifiutil.Psk{Password: testPass},
devicePolicy: wpaConfigDevicePolicy,
testUserSession: false,
},
},
{
// Verify that DUT can connect to a WPA-PSK AP inside user's session.
Name: "wpa_psk_wifi_user_session",
Val: policyBlockedWifiTestcase{
apConfig: wpa.NewConfigFactory(
testPass,
wpa.Mode(wpa.ModePureWPA2),
wpa.Ciphers2(wpa.CipherCCMP),
),
wifiCred: &wifiutil.Psk{Password: testPass},
devicePolicy: wpaConfigDevicePolicy,
testUserSession: true,
},
},
},
})
}
/*
PolicyBlockedWifi verifies if the DUT respects blocked WiFi networks from the enterprise provisioned per-device policies.
ONC spec: https://chromium.googlesource.com/chromium/src/+/main/components/onc/docs/onc_spec.md
Here are the full steps this test takes:
1. Test will be executed for AP with WPA-PSK security and for AP without security.
2. Start 3 APs, one will be blockedSSID by policy,
second will be blockedPreferredSSID - blocked by policy and same time added as recommended by policy,
third one will be notBlockedSSID at all.
3. Provision the per-device policy to the DUT with BlockedSSIDHex and BlockedPreferredSSIDHex in blocked SSIDs.
4. Wait for the DUT to connect to the AP provisioned with the device policy and check that before login to
user's session all 3 AP can be connected using quick settings.
5. Log in the user account, verify that only notBlockedSSID AP and blockedPreferredSSID can be connected using
quick settings and OS settings.
Verify that blockedSSID can not be connected and has special message when you open details view.
6. Clean up test, including disconnect, remove SSID entry and deconfig APs.
*/
func PolicyBlockedWifi(ctx context.Context, s *testing.State) {
params := s.Param().(policyBlockedWifiTestcase)
testFixture := s.FixtValue().(*wificell.TestFixture)
rpcClient := testFixture.DUTRPC(wificell.DefaultDUT)
policyClient := ps.NewPolicyServiceClient(rpcClient.Conn)
wifiSvc := testFixture.DUTWifiClient(wificell.DefaultDUT)
longerCtx, cancel := ctxutil.Shorten(ctx, 30*time.Second)
defer cancel()
localCtx := localContext{longerCtx, rpcClient.Conn, wifiSvc, params.wifiCred}
// Configure 3 access points for blocked, non blocked, blocked+preferred SSIDs.
nonBlockedApOptions := []ap.Option{ap.Mode(ap.Mode80211nMixed), ap.Channel(108), ap.HTCaps(ap.HTCapHT20), ap.SpectrumManagement(), ap.SSID(notBlockedSSID)}
blockedApOptions := []ap.Option{ap.Mode(ap.Mode80211nPure), ap.Channel(1), ap.HTCaps(ap.HTCapHT20), ap.SSID(blockedSSID)}
blockedPreferredApOptions := []ap.Option{ap.Mode(ap.Mode80211g), ap.Channel(1), ap.SSID(blockedPreferredSSID)}
deconfigAP := func(ctx context.Context, ap *wificell.APIface) {
if err := testFixture.DeconfigAP(ctx, ap); err != nil {
s.Error("Failed to deconfig access point, err: ", err)
}
}
notBlockedAP, err := testFixture.ConfigureAP(ctx, nonBlockedApOptions, params.apConfig)
if err != nil {
s.Fatal("Failed to configure access point for non blocked SSID, err: ", err)
}
cleanupCtxNotBlockedAP := ctx
defer deconfigAP(cleanupCtxNotBlockedAP, notBlockedAP)
ctx, cancelNotBlockedAP := testFixture.ReserveForDeconfigAP(ctx, notBlockedAP)
defer cancelNotBlockedAP()
blockedAP, err := testFixture.ConfigureAP(ctx, blockedApOptions, params.apConfig)
if err != nil {
s.Fatal("Failed to configure access point for blocked SSID, err: ", err)
}
cleanupCtxBlockedAP := ctx
defer deconfigAP(cleanupCtxBlockedAP, blockedAP)
ctx, cancelBlockedAP := testFixture.ReserveForDeconfigAP(ctx, blockedAP)
defer cancelBlockedAP()
blockedAndPreferredAP, err := testFixture.ConfigureAP(ctx, blockedPreferredApOptions, params.apConfig)
if err != nil {
s.Fatal("Failed to configure access point for blocked+preferred SSID, err: ", err)
}
cleanupCtxBlockedAndPreferredAP := ctx
defer deconfigAP(cleanupCtxBlockedAndPreferredAP, blockedAndPreferredAP)
ctx, cancelBlockedAndPreferredAP := testFixture.ReserveForDeconfigAP(ctx, blockedAndPreferredAP)
defer cancelBlockedAndPreferredAP()
accessPoints := make(map[string]*wificell.APIface)
accessPoints[notBlockedSSID] = notBlockedAP
accessPoints[blockedSSID] = blockedAP
accessPoints[blockedPreferredSSID] = blockedAndPreferredAP
// End configure 3 access points for blocked, non blocked, blocked+preferred SSIDs.
// Re-scan WiFi networks to make sure that SSIDs for the created APs (access
// points) are visible in the DUT's network setting.
req := &wifi.RequestScansRequest{Count: 1}
if _, err := wifiSvc.RequestScans(ctx, req); err != nil {
s.Fatal("Failed to request scan: ", err)
}
if _, err := wifiSvc.WaitScanIdle(ctx, &empty.Empty{}); err != nil {
s.Fatal(scanWaitingFailure, err)
}
startChromeRequest := func(keepState bool) *ui.NewRequest {
req := &ui.NewRequest{
LoginMode: ui.LoginMode_LOGIN_MODE_NO_LOGIN,
SigninProfileTestExtensionId: s.RequiredVar("ui.signinProfileTestExtensionManifestKey"),
KeepState: keepState,
}
return req
}
chromeService := ui.NewChromeServiceClient(rpcClient.Conn)
// Clean policy and shill at the end of test even it is failing early.
cleanPolicyAfterTest := func(localCtx localContext) {
s.Log("Cleaning policies")
ctx = localCtx.ctx
if _, err := chromeService.New(ctx, startChromeRequest(false /*keepState*/)); err != nil {
s.Fatal("Failed to start Chrome: ", err)
}
}
defer cleanPolicyAfterTest(localCtx)
cleanShillAfterTest := func(localCtx localContext) {
s.Log("Cleaning shill")
if err := cleanAllSSIDDataFromShill(localCtx); err != nil {
s.Fatal("Failed to clean SSIDs: ", err)
}
}
defer cleanShillAfterTest(localCtx)
s.Log("Start testing WiFi connection even before policy is applied")
func() {
ctx, cancel := ctxutil.Shorten(ctx, 25*time.Second)
cleanupCtx := ctx
defer cancel()
// This will reset chrome and all policies.
if _, err := chromeService.New(ctx, startChromeRequest(false /*keepsState*/)); err != nil {
s.Fatal(failedToStartChrome, err)
}
defer chromeService.Close(cleanupCtx, &emptypb.Empty{})
if err := testJoinWithOneClickBeforeLogin(localCtx, accessPoints); err != nil {
s.Fatal(canNotJoinWiFiPreTest, err)
}
}()
// Enroll device and apply policies from pJSON.
if err := cleanShillAndReEnroll(localCtx, params.devicePolicy, policyClient); err != nil {
s.Fatal("Failed to clean shill and enroll DUT with policies: ", err)
}
func() {
cleanupCtx := ctx
ctx, cancel := ctxutil.Shorten(ctx, 25*time.Second)
defer cancel()
if _, err := chromeService.New(ctx, startChromeRequest(true /*keepsState*/)); err != nil {
s.Fatal("Failed to start Chrome: ", err)
}
defer chromeService.Close(cleanupCtx, &emptypb.Empty{})
if !params.testUserSession {
// Run WiFi connections tests with applied policy on the login screen.
runTestBeforeLoginToSession(localCtx, s, accessPoints)
} else {
// Connect to blockedAP, so can verify that it is disconnected after the login.
if err := expectSuccAddAndJoinWiFiQuickSettings(blockedAP, localCtx); err != nil {
s.Fatal(canNotAddNewWiFi, err)
}
// Login and run WiFi connections tests with applied policy after the login
// to users session.
runTestAfterLoginToSession(localCtx, s, params.devicePolicy, policyClient, accessPoints)
}
}()
}
// runTestBeforeLoginToSession runs WiFi connections tests without and with
// applied policy on login screen without user's session.
func runTestBeforeLoginToSession(localCtx localContext,
s *testing.State,
accessPoints map[string]*wificell.APIface) {
// ------ Testing join WiFi with one click from the quick settings on the
// login screen.
s.Log("Start testing WiFi connection from one click in quick settings before login")
// Connect to blocked WiFi which is also defined in ONC policy.
// This WiFi has password/certificate defined in policy, so we should be able
// to join it without entering any password.
if err := expectSuccJoinWiFi(accessPoints[blockedPreferredSSID], localCtx, quickSettings, !expectDialog); err != nil {
s.Fatal(canNotJoinWiFi, err)
}
// Connect and test connection to the notBlockedAP.
if err := expectSuccJoinWiFi(accessPoints[notBlockedSSID], localCtx, quickSettings, expectDialog); err != nil {
s.Fatal(canNotJoinWiFi, err)
}
// Connect and test connection to the blockedAP.
if err := expectSuccJoinWiFi(accessPoints[blockedSSID], localCtx, quickSettings, expectDialog); err != nil {
s.Fatal(canNotJoinWiFi, err)
}
// ------ End testing one click join on Login screen.
// ------- Test join WiFi from "Add a new WiFi connection" in quick settings on the login screen.
// For every access point we try to open "Add a new WiFi connection" from the quick
// settings and manually put the name, security/password and then click Connect.
s.Log("Start testing add new WiFi connection from the quick settings before login")
// Connect and test connection to the notBlockedAP.
if err := expectSuccAddAndJoinWiFiQuickSettings(accessPoints[notBlockedSSID], localCtx); err != nil {
s.Fatal(canNotAddNewWiFi, err)
}
// Connect and test connection to the blockedAP.
if err := expectSuccAddAndJoinWiFiQuickSettings(accessPoints[blockedSSID], localCtx); err != nil {
s.Fatal(canNotAddNewWiFi, err)
}
// Connect and test connection to the blockedAndPreferredAP.
// WiFi popup shows error, previous network is still connected, this is a bug.
// TODO(b/278189326): Change this to joinAndTestWiFiFromQuickSettings() after bug is fixed.
if err := expectFailAddAndJoinWiFiQuickSettings(accessPoints[blockedPreferredSSID], localCtx); err != nil {
s.Fatal("Expected to fail while joining blocked and preferred WiFi from quick settings: ", err)
}
}
// runTestAfterLoginToSession runs WiFi connections tests with applied policy
// after the login to users session.
func runTestAfterLoginToSession(localCtx localContext,
s *testing.State,
devicePolicy *policy.DeviceOpenNetworkConfiguration,
policyClient ps.PolicyServiceClient,
accessPoints map[string]*wificell.APIface) {
ctx := localCtx.ctx
wifiSvc := localCtx.wifiSvc
s.Log("Start testing WiFi connection with applied policies after the login")
// Get chrome service and login to users session with default test user.
// Policies were applied on device level, so they will be efficient for any logged in user.
cleanupCtx := ctx
ctx, cancel := ctxutil.Shorten(ctx, 25*time.Second)
defer cancel()
chromeService := ui.NewChromeServiceClient(localCtx.rpcClient)
if _, err := chromeService.New(ctx, &ui.NewRequest{KeepState: true}); err != nil {
s.Fatal("DUT: failed to start Chrome: ", err)
}
// Check that blockedAP is disconnected after login.
if err := expectWiFiNotConnected(ctx, wifiSvc, accessPoints[blockedSSID].Config().SSID); err != nil {
s.Fatal(err, "failed to verify that blocked WiFi was disconnected after login")
}
// --------- Testing join WiFi with one click from quick settings after login.
// Removed stored passwords from shill and re-enroll DUT with policies to recover shill state.
if _, err := chromeService.Close(cleanupCtx, &emptypb.Empty{}); err != nil {
s.Fatal("Failed to close chrome: ", err)
}
if err := cleanShillAndReEnroll(localCtx, devicePolicy, policyClient); err != nil {
s.Fatal("Failed to clean shill and enroll DUT with policies: ", err)
}
chromeService = ui.NewChromeServiceClient(localCtx.rpcClient)
if _, err := chromeService.New(ctx, &ui.NewRequest{KeepState: true}); err != nil {
s.Fatal(failedToStartChrome, err)
}
s.Log("Start testing WiFi connection from one click settings login")
// Connect and test connection to the notBlockedAP.
if err := expectSuccJoinWiFi(accessPoints[notBlockedSSID], localCtx, quickSettings, expectDialog); err != nil {
s.Fatal(canNotJoinWiFi, err)
}
// Connect to blocked WiFi which is also defined in ONC policy.
// This WiFi has password/certificate defined in policy, so we should be able
// to join this WiFi without entering any password.
if err := expectSuccJoinWiFi(accessPoints[blockedPreferredSSID], localCtx, quickSettings, !expectDialog); err != nil {
s.Fatal(canNotJoinWiFi, err)
}
// Can not connect to blockedAP.
if err := expectFailJoinWiFiFromOneClick(accessPoints[blockedSSID], localCtx); err != nil {
s.Fatal("Was expecting failure for joining WiFi, but received: ", err)
}
// --------- End join WiFi with one click from quick settings after login.
// --------- Testing join WiFi with one click from OS settings after login.
s.Log("Start testing WiFi connection from OS settings after login with one click")
// Removed stored passwords from shill and re-enroll DUT with policies to recover shill state.
if _, err := chromeService.Close(cleanupCtx, &emptypb.Empty{}); err != nil {
s.Fatal("Failed to close chrome: ", err)
}
if err := cleanShillAndReEnroll(localCtx, devicePolicy, policyClient); err != nil {
s.Fatal("Failed to clean shill and enroll DUT with policies: ", err)
}
chromeService = ui.NewChromeServiceClient(localCtx.rpcClient)
if _, err := chromeService.New(ctx, &ui.NewRequest{KeepState: true}); err != nil {
s.Fatal(failedToStartChrome, err)
}
// Connect and test connection to the notBlockedAP.
if err := expectSuccJoinWiFi(accessPoints[notBlockedSSID], localCtx, osSettings, expectDialog); err != nil {
s.Fatal(canNotJoinWiFi, err)
}
// Connect and test connection to the blockedAndPreferredAP.
if err := expectSuccJoinWiFi(accessPoints[blockedPreferredSSID], localCtx, osSettings, !expectDialog); err != nil {
s.Fatal(canNotJoinWiFi, err)
}
// Connect and test connection to the blockedAP.
if err := expectBlockedJoinWiFiOSSettings(ctx, localCtx.rpcClient, accessPoints[blockedSSID]); err != nil {
s.Fatal("Was expecting to find blocked WiFi, but received: ", err)
}
//--------- End join WiFi with one click from OS settings after login.
//------- Test join WiFi from "Add a new WiFi connection" in quick settings after login.
s.Log("Start testing add WiFi connection from quick settings after login")
// Connect and test connection to the notBlockedAP.
if err := expectSuccAddAndJoinWiFiQuickSettings(accessPoints[notBlockedSSID], localCtx); err != nil {
s.Fatal("Failed to join not blocked WiFi from quick settings: ", err)
}
// Connect and test connection to the blockedAP.
// WiFi popup expected to show error because network is blocked by policy.
if err := expectFailAddAndJoinWiFiQuickSettings(accessPoints[blockedSSID], localCtx); err != nil {
s.Fatal("Expected to fail while joining blocked WiFi from quick settings: ", err)
}
// Connect and test connection to the blockedAndPreferredAP.
// WiFi popup shows error, this is a bug.
// TODO(b/278189326): Change this to joinAndTestWiFiFromQuickSettings() after bug is fixed.
if err := expectFailAddAndJoinWiFiQuickSettings(accessPoints[blockedPreferredSSID], localCtx); err != nil {
s.Fatal("Expected to fail while joining not blocked prefered WiFi from quick settings: ", err)
}
}
// expectBlockedJoinWiFiOSSettings verifies that provided network is disabled by administrator.
func expectBlockedJoinWiFiOSSettings(ctx context.Context, conn *grpc.ClientConn, expectedAP *wificell.APIface) (retErr error) {
cleanupCtx := ctx
ctx, cancel := ctxutil.Shorten(ctx, 10*time.Second)
defer cancel()
uiSvc := ui.NewAutomationServiceClient(conn)
ssid := expectedAP.Config().SSID
cleanup, err := openNetworkSettingPage(ctx, conn, ssid)
defer cleanup(cleanupCtx)
if err != nil {
return errors.Wrap(err, "failed to open network settings page")
}
networkStatusText := ui.Node().Name("Connecting to this network is disabled by your administrator").Role(ui.Role_ROLE_STATIC_TEXT).Finder()
exists, err := wifiutil.StableCheckIsNodeFound(ctx, uiSvc, networkStatusText)
if err != nil {
return errors.Wrap(err, "error during finding text about disabled network")
}
if exists != true {
return errors.New("failed to find text about disabled network")
}
return nil
}
// expectFailJoinWiFiFromOneClick verifies that tooltip about disable network is shown for blocked networks.
func expectFailJoinWiFiFromOneClick(expectedAP *wificell.APIface, localCtx localContext) (retErr error) {
uiSvc := ui.NewAutomationServiceClient(localCtx.rpcClient)
ssid := expectedAP.Config().SSID
keyboard := inputs.NewKeyboardServiceClient(localCtx.rpcClient)
ctx := localCtx.ctx
wifiSvc := localCtx.wifiSvc
cleanupCtx := ctx
quickSettingsSvc := quicksettings.NewQuickSettingsServiceClient(localCtx.rpcClient)
if _, err := quickSettingsSvc.NavigateToNetworkDetailedView(ctx, &emptypb.Empty{}); err != nil {
return errors.Wrap(err, "failed to navigate to network detailed view within the Quick Settings")
}
cleanup := func(ctx context.Context) { quickSettingsSvc.Hide(ctx, &emptypb.Empty{}) }
defer cleanup(cleanupCtx)
if _, err := wifiSvc.WaitScanIdle(ctx, &empty.Empty{}); err != nil {
return errors.Wrap(err, scanWaitingFailure)
}
connectToSsidButton := ui.Node().Name(fmt.Sprintf("Connect to %s", ssid)).Role(ui.Role_ROLE_BUTTON).Finder()
if _, err := uiSvc.WaitUntilExists(ctx, &ui.WaitUntilExistsRequest{Finder: connectToSsidButton}); err != nil {
return errors.Wrap(err, "failed to ensure the SSID connection button is present")
}
if err := ensureUIElementVisible(ctx, uiSvc, keyboard, connectToSsidButton); err != nil {
return errors.Wrap(err, "failed select required SSID")
}
if _, err := uiSvc.LeftClick(ctx, &ui.LeftClickRequest{Finder: connectToSsidButton}); err != nil {
return errors.Wrap(err, "failed to click the network from detail list")
}
disableTooltip := ui.Node().NameContaining("This network is disabled by your administrator").Role(ui.Role_ROLE_TOOLTIP).Finder()
if _, err := uiSvc.WaitUntilExists(ctx, &ui.WaitUntilExistsRequest{Finder: disableTooltip}); err != nil {
// This is bug for boards with mobile data - tooltip about disabled network
// is not shown, accepting it because this info is available via OS settings.
// b/297339385.
mobileData := ui.Node().NameContaining("Mobile data").Role(ui.Role_ROLE_BUTTON).Finder()
if _, err := uiSvc.WaitUntilExists(ctx, &ui.WaitUntilExistsRequest{Finder: mobileData}); err != nil {
return errors.Wrap(err, "error during searching disabled network tooltip")
}
}
if err := expectWiFiNotConnected(ctx, wifiSvc, ssid); err != nil {
return errors.Wrap(err, "failed to verify that WiFi was connected")
}
return nil
}
// expectSuccAddAndJoinWiFiQuickSettings verifies that attempt to join WiFi from quick settings
// "Add WiFi network" was successful and cleans entered data from shill.
func expectSuccAddAndJoinWiFiQuickSettings(accessPoint *wificell.APIface, localCtx localContext) (retErr error) {
ctx := localCtx.ctx
wifiSvc := localCtx.wifiSvc
cleanupCtx := ctx
ssid := accessPoint.Config().SSID
cleanup, err := wifiutil.JoinWifiFromQuickSettings(ctx, localCtx.rpcClient, ssid, localCtx.wifiCred)
defer cleanup(cleanupCtx)
if err != nil {
return errors.Wrap(err, "failed open and fill WiFi data from quick settings")
}
if err := expectWiFiConnected(ctx, wifiSvc, ssid); err != nil {
return errors.Wrap(err, connectionVerificationFailure)
}
return nil
}
// expectFailAddAndJoinWiFiQuickSettings verifies that attempt to join WiFi failed and cleans entered data from shill.
func expectFailAddAndJoinWiFiQuickSettings(accessPoint *wificell.APIface, localCtx localContext) (retErr error) {
ctx := localCtx.ctx
wifiSvc := localCtx.wifiSvc
cleanupCtx := ctx
ssid := accessPoint.Config().SSID
cleanup, err := wifiutil.JoinWifiFromQuickSettings(ctx, localCtx.rpcClient, ssid, localCtx.wifiCred)
defer cleanup(cleanupCtx)
if err != nil {
return errors.Wrap(err, "failed open and fill WiFi data from quick settings")
}
if err := expectNetworkConfigErrorPrompted(ctx, localCtx.rpcClient); err != nil {
return errors.Wrap(err, "failed to find the expected network configuration error message")
}
if err := expectWiFiNotConnected(ctx, wifiSvc, ssid); err != nil {
return errors.Wrap(err, "failed to verify that WiFi was not connected")
}
return nil
}
// expectNetworkConfigErrorPrompted verifies a message "Error configuring network" on the "Join WiFi Network" dialog.
func expectNetworkConfigErrorPrompted(ctx context.Context, conn *grpc.ClientConn) error {
uiSvc := ui.NewAutomationServiceClient(conn)
networkConfigErrorMsg := ui.Node().Name("Error configuring network").Role(ui.Role_ROLE_INLINE_TEXT_BOX).Ancestor(wifiutil.JoinWiFiNetworkDialogFinder).Finder()
if _, err := uiSvc.WaitUntilExists(ctx, &ui.WaitUntilExistsRequest{Finder: networkConfigErrorMsg}); err != nil {
return errors.Wrap(err, "failed to find the expected error message")
}
return nil
}
// expectWiFiConnected waits the specified network is connected.
func expectWiFiConnected(ctx context.Context, wifiSvc *wificell.WifiClient, ssid string) error {
return wifiSvc.WaitForConnected(ctx, ssid, true /* expectedValue */)
}
// expectWiFiNotConnected waits the specified network is not connected.
func expectWiFiNotConnected(ctx context.Context, wifiSvc *wificell.WifiClient, ssid string) error {
return wifiSvc.WaitForConnected(ctx, ssid, false /* expectedValue */)
}
// openNetworkSettingPage will open specific SSID page from the OS settings.
func openNetworkSettingPage(ctx context.Context, conn *grpc.ClientConn, ssid string) (func(context.Context), error) {
osSettingsSvc := ossettings.NewOsSettingsServiceClient(conn)
cleanup := func(ctx context.Context) { osSettingsSvc.Close(ctx, &emptypb.Empty{}) }
uiSvc := ui.NewAutomationServiceClient(conn)
// SSIDs positon might change due to signal strengths, so will re-try from
// here if the page header is not correct.
for i := 1; i <= 3; i++ {
if _, err := osSettingsSvc.OpenNetworkDetailPage(ctx, &ossettings.OpenNetworkDetailPageRequest{
NetworkName: ssid,
NetworkType: ossettings.OpenNetworkDetailPageRequest_WIFI,
}); err != nil {
return cleanup, errors.Wrap(err, "error during opening networks setting page")
}
// Check that page header has required SSID.
pageHeaderName := ui.Node().Name(ssid).Role(ui.Role_ROLE_STATIC_TEXT).Finder()
correctPage, err := wifiutil.StableCheckIsNodeFound(ctx, uiSvc, pageHeaderName)
if err != nil {
return cleanup, errors.Wrap(err, "error during searching page header on network setting page")
}
if correctPage == true {
return cleanup, nil
}
}
return cleanup, errors.New("Expected page header was not found " + ssid)
}
// expectSuccJoinWiFi will joind WiFi, check that WiFi is connected and clean data afterwards.
func expectSuccJoinWiFi(expectedAP *wificell.APIface, localCtx localContext, settingType int, isCredentialsRequired bool) (retErr error) {
ctx := localCtx.ctx
wifiSvc := localCtx.wifiSvc
if _, err := wifiSvc.WaitScanIdle(ctx, &empty.Empty{}); err != nil {
return errors.Wrap(err, scanWaitingFailure)
}
var err error
cleanupCtx := ctx
ssid := expectedAP.Config().SSID
for retryAttempt := 0; retryAttempt < 2; retryAttempt++ {
cleanup, err := joinWiFiWithOneClick(localCtx, ssid, testPass, settingType, isCredentialsRequired)
defer cleanup(cleanupCtx)
if err == nil {
break
}
}
if err != nil {
return errors.Wrap(err, "failed open and fill WiFi data from one click")
}
for retryAttempt := 0; retryAttempt < 2; retryAttempt++ {
err := expectWiFiConnected(ctx, wifiSvc, ssid)
if err == nil {
break
}
}
if err != nil {
return errors.Wrap(err, connectionVerificationFailure)
}
return nil
}
// joinWiFiWithOneClick will joind WiFi network from quick settings or OS settings with one click.
func joinWiFiWithOneClick(localCtx localContext, ssid, password string, settingType int, isCredentialsRequired bool) (func(context.Context), error) {
ctx := localCtx.ctx
rpcClient := localCtx.rpcClient
cleanup, err := openJoinWiFiDialogFromOneClick(ctx, rpcClient, settingType, ssid)
if err != nil {
faillog := ui.NewChromeUIServiceClient(rpcClient)
faillog.DumpUITreeWithScreenshotToFile(ctx, &ui.DumpUITreeWithScreenshotToFileRequest{
FilePrefix: "network_details_page",
})
return cleanup, errors.Wrap(err, `failed to open "Join Wi-Fi network" dialog"`)
}
if _, ok := localCtx.wifiCred.(*wifiutil.None); ok {
return cleanup, nil
}
if !isCredentialsRequired {
return cleanup, nil
}
uiauto := ui.NewAutomationServiceClient(rpcClient)
if _, err := uiauto.LeftClick(ctx, &ui.LeftClickRequest{Finder: wifiutil.JoinWiFiNetworkDialogFinder}); err != nil {
return cleanup, errors.Wrap(err, "failed to click the join button")
}
if err := wifiutil.CompleteJoinWiFiDialog(ctx, rpcClient, ssid, localCtx.wifiCred, true /*skipSecurityComboBox*/); err != nil {
return cleanup, errors.Wrap(err, "failed to join Wi-Fi")
}
return cleanup, nil
}
// openJoinWiFiDialogFromOneClick will select specific SSID from the WiFi list in
// OS settings or in Quick settings and press enter on it, which should lead
// to "join WiFi" dialog window.
func openJoinWiFiDialogFromOneClick(ctx context.Context, rpcClient *grpc.ClientConn, settings int, ssid string) (func(context.Context), error) {
keyboard := inputs.NewKeyboardServiceClient(rpcClient)
uiSvc := ui.NewAutomationServiceClient(rpcClient)
var cleanup func(context.Context)
var connectToSsidButton *ui.Finder
if settings == quickSettings {
settingsSvc := quicksettings.NewQuickSettingsServiceClient(rpcClient)
if _, err := settingsSvc.NavigateToNetworkDetailedView(ctx, &emptypb.Empty{}); err != nil {
return func(ctx context.Context) { return }, errors.Wrap(err, "failed to navigate to network detailed view within the Quick Settings")
}
cleanup = func(ctx context.Context) { settingsSvc.Hide(ctx, &emptypb.Empty{}) }
connectToSsidButton = ui.Node().Name(fmt.Sprintf("Connect to %s", ssid)).Role(ui.Role_ROLE_BUTTON).Finder()
} else if settings == osSettings {
settingsSvc := ossettings.NewOsSettingsServiceClient(rpcClient)
if _, err := settingsSvc.LaunchAtWifiPage(ctx, &emptypb.Empty{}); err != nil {
return func(ctx context.Context) { return }, errors.Wrap(err, "error during opening OS WiFi setting page")
}
cleanup = func(ctx context.Context) { settingsSvc.Close(ctx, &emptypb.Empty{}) }
connectToSsidButton = &ui.Finder{
NodeWiths: []*ui.NodeWith{
{Value: &ui.NodeWith_NameContaining{NameContaining: fmt.Sprintf(`, %s,`, ssid)}},
{Value: &ui.NodeWith_First{First: true}},
},
}
}
if _, err := uiSvc.WaitUntilExists(ctx, &ui.WaitUntilExistsRequest{Finder: connectToSsidButton}); err != nil {
return cleanup, errors.Wrap(err, "failed to ensure the SSID connection button is present")
}
if err := ensureUIElementVisible(ctx, uiSvc, keyboard, connectToSsidButton); err != nil {
return cleanup, errors.Wrap(err, "failed select required SSID")
}
if _, err := uiSvc.EnsureFocused(ctx, &ui.EnsureFocusedRequest{Finder: connectToSsidButton}); err != nil {
return cleanup, errors.Wrap(err, "required SSID was not found")
}
if _, err := keyboard.Accel(ctx, &inputs.AccelRequest{Key: "enter"}); err != nil {
return cleanup, errors.Wrap(err, "failed to select required SSID")
}
return cleanup, nil
}
// ensureUIElementVisible moves UI element which is offscreen to visible by pressing
// Tab until UI element is visible. It is added for more stable tests in noisy environment
// when list of WiFis is very long.
func ensureUIElementVisible(ctx context.Context, uiSvc ui.AutomationServiceClient,
keyboard inputs.KeyboardServiceClient, uiElement *ui.Finder) error {
// Focus on current window/tab by pressing Tab once.
if _, err := keyboard.Accel(ctx, &inputs.AccelRequest{Key: "tab"}); err != nil {
return errors.Wrap(err, tabPressFailure)
}
isSsidFocusable, err := isUIElementVisible(ctx, uiSvc, uiElement)
if err != nil {
return errors.Wrap(err, "UI element can not be seen on screen")
}
// SSID is not visible on screen, click Tab until it become visible.
if !isSsidFocusable {
// Move from the top of menu to the WiFi list.
for i := 1; i <= 6; i++ {
if _, err := keyboard.Accel(ctx, &inputs.AccelRequest{Key: "tab"}); err != nil {
return errors.Wrap(err, tabPressFailure)
}
}
maxAttempForTabPress := 30
for i := 1; i <= maxAttempForTabPress; i++ {
isSsidFocusable, err := isUIElementVisible(ctx, uiSvc, uiElement)
if err != nil {
return errors.Wrap(err, "UI element can not be focused")
}
if isSsidFocusable {
break
} else {
if _, err := keyboard.Accel(ctx, &inputs.AccelRequest{Key: "tab"}); err != nil {
return errors.Wrap(err, tabPressFailure)
}
}
}
}
// On the board with small screen last SSID will be still at the bottom of quick settings
// widget and not visible in VNC, so pressing one more tab to make it visible on screen.
// The last element in list is "Add new networks", so in the worst case pressing tab will move selection to it.
if _, err := keyboard.Accel(ctx, &inputs.AccelRequest{Key: "tab"}); err != nil {
return errors.Wrap(err, tabPressFailure)
}
return nil
}
// isUIElementVisible checks if UI element has state "offscreen" in their NodeInfo.
func isUIElementVisible(ctx context.Context, uiSvc ui.AutomationServiceClient, uiElement *ui.Finder) (bool, error) {
uiElementInfo, err := wifiutil.StableNodeInfo(ctx, uiSvc, uiElement)
if err != nil {
return false, errors.Wrap(err, "failed to check UI element information")
}
val, isSsidOffScreen := uiElementInfo.NodeInfo.State["offscreen"]
if isSsidOffScreen && val {
return false, nil
} else if isSsidOffScreen && !val {
return false, errors.Errorf("the current UI element offscreen: %s, got offscreen: %t, expect offscreen: %t", uiElementInfo.NodeInfo.Restriction, isSsidOffScreen, true)
} else {
return true, nil
}
}
// enrollDut enrolls DUT with policies. It will also re-try enroll
// if first attempt has failed. Retry is not very efficient because usually
// second attempt also fails, but in some cases can help.
func enrollDut(ctx context.Context, devicePolicy *policy.DeviceOpenNetworkConfiguration, policyClient ps.PolicyServiceClient) error {
cleanupCtx := ctx
ctx, cancel := ctxutil.Shorten(cleanupCtx, 20*time.Second)
defer cancel()
var err error
// Prepare policy for device.
var pJSON []byte
policyBlob := policy.NewBlob()
policyBlob.AddPolicy(devicePolicy)
pJSON, err = json.Marshal(policyBlob)
if err != nil {
return err
}
retryAttempt := 0
for retryAttempt < 2 {
retryAttempt++
_, err = policyClient.EnrollUsingChrome(ctx, &ps.EnrollUsingChromeRequest{
PolicyJson: pJSON,
SkipLogin: true,
})
defer policyClient.StopChromeAndFakeDMS(cleanupCtx, &empty.Empty{})
if err == nil {
break
}
}
return err
}
// cleanAllSSIDDataFromShill cleans all known SSIDs data from Shill,
// so WiFi become unknown to DUT again. This will also clean policy defined WiFi
// from Shill, so it can be executed only at the very end of tests or you need
// to apply policy again.
func cleanAllSSIDDataFromShill(localCtx localContext) error {
ctx := localCtx.ctx
wifiSvc := localCtx.wifiSvc
for _, ssid := range allSSIDs {
req := &wifi.DeleteEntriesForSSIDRequest{Ssid: []byte(ssid)}
if _, err := wifiSvc.DeleteEntriesForSSID(ctx, req); err != nil {
return errors.Wrap(err, ssidDataRemovalFailure)
}
}
return nil
}
// cleanShillAndReEnroll removes all data from shill and re-enroll DUT with
// policies.
func cleanShillAndReEnroll(localCtx localContext, devicePolicy *policy.DeviceOpenNetworkConfiguration,
policyClient ps.PolicyServiceClient) error {
if err := cleanAllSSIDDataFromShill(localCtx); err != nil {
return err
}
ctx := localCtx.ctx
if err := enrollDut(ctx, devicePolicy, policyClient); err != nil {
return err
}
return nil
}
// testJoinWithOneClickBeforeLogin testing join WiFi with one click from quick settings on login screen before
// policies are applied.
func testJoinWithOneClickBeforeLogin(localCtx localContext, accessPoints map[string]*wificell.APIface) (retErr error) {
// Connect to the wifi which will be blocked by ONC policy.
if err := expectSuccJoinWiFi(accessPoints[blockedSSID], localCtx, quickSettings, expectDialog); err != nil {
return err
}
// lockedAndPreferredAP is included into policy, but there should be no policy yet.
if err := expectSuccJoinWiFi(accessPoints[blockedPreferredSSID], localCtx, quickSettings, expectDialog); err != nil {
return err
}
// Connect to the notBlockedAP.
if err := expectSuccJoinWiFi(accessPoints[notBlockedSSID], localCtx, quickSettings, expectDialog); err != nil {
return err
}
// Connect to the wifi which will be blocked by ONC policy.
if err := expectSuccJoinWiFi(accessPoints[blockedSSID], localCtx, quickSettings, !expectDialog); err != nil {
return err
}
// Connect to the notBlockedAP.
if err := expectSuccJoinWiFi(accessPoints[notBlockedSSID], localCtx, quickSettings, !expectDialog); err != nil {
return err
}
return nil
}