blob: ef1c4a7a85e0877c8c529a9edeb5b1911e6ee22b [file] [log] [blame]
// Copyright 2020 The Chromium OS Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
package assistant
import (
func init() {
Func: BluetoothQueries,
Desc: "Tests toggling Bluetooth using Assistant queries",
Contacts: []string{"", ""},
Attr: []string{"group:mainline", "informational"},
SoftwareDeps: []string{"chrome"},
Pre: assistant.VerboseLoggingEnabled(),
// BluetoothQueries tests that Assistant queries can be used to toggle Bluetooth on/off
func BluetoothQueries(ctx context.Context, s *testing.State) {
cr := s.PreValue().(*chrome.Chrome)
tconn, err := cr.TestAPIConn(ctx)
if err != nil {
s.Fatal("Creating test API connection failed: ", err)
// Enable the Assistant and wait for the ready signal.
if err := assistant.EnableAndWaitForReady(ctx, tconn); err != nil {
s.Fatal("Failed to enable Assistant: ", err)
defer assistant.Disable(ctx, tconn)
// Open the Settings window, where we can verify Bluetooth/Wifi status
if err := apps.Launch(ctx, tconn, apps.Settings.ID); err != nil {
s.Fatal("Failed to launch Settings app: ", err)
// Turn settings on, off, and on again to ensure they can be enabled and disabled, regardless of starting state
statuses := []bool{true, false, true}
var onOff string
for _, status := range statuses {
if status {
onOff = "on"
} else {
onOff = "off"
s.Log("Turning bluetooth ", onOff)
// assistant.SendTextQuery sometimes times out after the assistant UI is closed,
// so poll to ensure the queries go through.
// TODO(crbug/1080363): remove polling.
if err := testing.Poll(ctx, func(ctx context.Context) error {
_, err := assistant.SendTextQuery(ctx, tconn, fmt.Sprintf("turn bluetooth %v", onOff))
return err
}, nil); err != nil {
s.Fatal("Failed to get Assistant bluetooth query response: ", err)
s.Log("Checking Bluetooth status using dbus")
if err := testing.Poll(ctx, func(ctx context.Context) error {
if enabled, err := bluetoothEnabled(ctx); err != nil {
return testing.PollBreak(err)
} else if enabled != status {
return errors.Wrapf(err, "incorrect bluetooth state (expected: %v, actual: %v", status, enabled)
return nil
}, nil); err != nil {
s.Fatal("Failed checking bluetooth status via dbus: ", err)
// Check if button in the Settings app UI updated to match the actual status.
// The buttons don't update immediately, so we'll need to poll their statuses.
// The "aria-pressed" htmlAttribute of the toggle buttons can be used to check the on/off status
s.Log("Checking bluetooth toggle button status")
if err := testing.Poll(ctx, func(ctx context.Context) error {
params := ui.FindParams{Name: "Bluetooth enable", Role: ui.RoleTypeToggleButton}
bluetoothToggle, err := ui.FindWithTimeout(ctx, tconn, params, 10*time.Second)
if err != nil {
defer bluetoothToggle.Release(ctx)
if bluetoothToggle.HTMLAttributes["aria-pressed"] != strconv.FormatBool(status) {
return errors.Errorf("bluetooth not toggled yet, aria-pressed is %v, expected %v",
bluetoothToggle.HTMLAttributes["aria-pressed"], status)
return nil
}, nil); err != nil {
s.Fatal("Bluetooth button (Settings app) was not toggled by the Assistant: ", err)
// Check Bluetooth quick setting pod as well.
s.Log("Checking bluetooth status in Quick Settings")
if btPodStatus, err := quicksettings.SettingEnabled(ctx, tconn, quicksettings.SettingPodBluetooth); err != nil {
s.Fatal("Failed to get Bluetooth quick setting status: ", err)
} else if btPodStatus != status {
s.Fatal("Bluetooth quick setting pod was not toggled by the Assistant")
// bluetoothEnabled checks if the bluetooth adapter is enabled using dbus
func bluetoothEnabled(ctx context.Context) (bool, error) {
adapters, err := bluetooth.Adapters(ctx)
if err != nil {
return false, errors.Wrap(err, "failed to get bluetooth adapters")
if len(adapters) != 1 {
return false, errors.Errorf("unexpected Bluetooth adapters count; got %d, want 1", len(adapters))
adapter := adapters[0]
return adapter.Powered(ctx)