blob: 2e5f7e058ad8cf72d5ee4a9a27dd5b5f4e53c5b4 [file] [log] [blame]
// Copyright 2021 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 cca provides utilities to interact with Chrome Camera App.
package cca
import (
"context"
"fmt"
"time"
"chromiumos/tast/errors"
"chromiumos/tast/local/coords"
"chromiumos/tast/testing"
)
// UIComponent represents a CCA UI component.
type UIComponent struct {
Name string
Selectors []string
}
var (
// CancelResultButton is button for canceling intent review result.
CancelResultButton = UIComponent{"cancel result button", []string{"#cancel-result", "button[i18n-label=cancel_review_button]"}}
// ConfirmResultButton is button for confirming intent review result.
ConfirmResultButton = UIComponent{"confirm result button", []string{"#confirm-result", "button[i18n-label=confirm_review_button]"}}
// MirrorButton is button used for toggling preview mirroring option.
MirrorButton = UIComponent{"mirror button", []string{"#toggle-mirror"}}
// ModeSelector is selection bar for different capture modes.
ModeSelector = UIComponent{"mode selector", []string{"#modes-group"}}
// SettingsButton is button for opening primary setting menu.
SettingsButton = UIComponent{"settings", []string{"#open-settings"}}
// SwitchDeviceButton is button for switching camera device.
SwitchDeviceButton = UIComponent{"switch device button", []string{"#switch-device"}}
// VideoSnapshotButton is button for taking video snapshot during recording.
VideoSnapshotButton = UIComponent{"video snapshot button", []string{"#video-snapshot"}}
// VideoPauseResumeButton is button for pausing or resuming video recording.
VideoPauseResumeButton = UIComponent{"video pause/resume button", []string{"#pause-recordvideo"}}
// GalleryButton is button for entering the Backlight app as a gallery for captured files.
GalleryButton = UIComponent{"gallery button", []string{"#gallery-enter"}}
// GalleryButtonCover is cover photo of gallery button.
GalleryButtonCover = UIComponent{"gallery button cover", []string{"#gallery-enter>img"}}
// PhotoResolutionSettingButton is button for opening photo resolution setting menu.
PhotoResolutionSettingButton = UIComponent{"photo resolution setting button", []string{"#settings-photo-resolution"}}
// PhotoAspectRatioSettingButton is button for opening photo aspect ratio setting menu.
PhotoAspectRatioSettingButton = UIComponent{"photo aspect ratio setting button", []string{"#settings-photo-aspect-ratio"}}
// VideoResolutionSettingButton is button for opening video resolution setting menu.
VideoResolutionSettingButton = UIComponent{"video resolution setting button", []string{"#settings-video-resolution"}}
// ResolutionSettingButton is button for opening resolution setting menu.
ResolutionSettingButton = UIComponent{"resolution setting button", []string{"#settings-resolution"}}
// ExpertModeButton is button used for opening expert mode setting menu.
ExpertModeButton = UIComponent{"expert mode button", []string{"#settings-expert"}}
// PhotoResolutionOption is option for each available photo capture resolution.
PhotoResolutionOption = UIComponent{"photo resolution option", []string{
"#view-photo-resolution-settings input"}}
// VideoResolutionOption is option for each available video capture resolution.
VideoResolutionOption = UIComponent{"video resolution option", []string{
"#view-video-resolution-settings input"}}
// FeedbackButton is the feedback button showing in the settings menu.
FeedbackButton = UIComponent{"feedback button", []string{"#settings-feedback"}}
// HelpButton is the help button showing in the settings menu.
HelpButton = UIComponent{"help button", []string{"#settings-help"}}
// GridTypeSettingsButton is the button showing in the settings menu which is used for entering the grid type settings menu.
GridTypeSettingsButton = UIComponent{"grid type settings button", []string{"#settings-gridtype"}}
// GoldenGridButton is the button to enable golden grid type.
GoldenGridButton = UIComponent{"golden grid type button", []string{"#grid-golden"}}
// TimerSettingsButton is the button showing in the settings menu which is used for entering the timer settings menu.
TimerSettingsButton = UIComponent{"timer settings button", []string{"#settings-timerdur"}}
// Timer10sButton is the button to enable 10s timer.
Timer10sButton = UIComponent{"timer 10s button", []string{"#timer-10s"}}
// BarcodeChipURL is chip for url detected from barcode.
BarcodeChipURL = UIComponent{"barcode chip url", []string{".barcode-chip-url a"}}
// BarcodeChipText is chip for text detected from barcode.
BarcodeChipText = UIComponent{"barcode chip text", []string{".barcode-chip-text"}}
// BarcodeCopyURLButton is button to copy url detected from barcode.
BarcodeCopyURLButton = UIComponent{"barcode copy url button",
[]string{"#barcode-chip-url-container .barcode-copy-button"}}
// BarcodeCopyTextButton is button to copy text detected from barcode.
BarcodeCopyTextButton = UIComponent{"barcode copy text button",
[]string{"#barcode-chip-text-container .barcode-copy-button"}}
// VideoProfileSelect is select-options for selecting video profile.
VideoProfileSelect = UIComponent{"video profile select", []string{"#video-profile"}}
// BitrateMultiplierRangeInput is range input for selecting bitrate multiplier.
BitrateMultiplierRangeInput = UIComponent{"bitrate multiplier range input", []string{"#bitrate-slider input[type=range]"}}
// OptionsContainer is the container for all options for opening option panel.
OptionsContainer = UIComponent{"container of options", []string{"#options-container"}}
// OpenMirrorPanelButton is the button which is used for opening the mirror state settings panel.
OpenMirrorPanelButton = UIComponent{"mirror state option button", []string{"#open-mirror-panel"}}
// OpenGridPanelButton is the button which is used for opening the grid type settings panel.
OpenGridPanelButton = UIComponent{"grid type option button", []string{"#open-grid-panel"}}
// OpenTimerPanelButton is the button which is used for opening the timer type settings panel.
OpenTimerPanelButton = UIComponent{"timer type option button", []string{"#open-timer-panel"}}
// OpenPTZPanelButton is the button for opening PTZ panel.
OpenPTZPanelButton = UIComponent{"open ptz panel button", []string{"#open-ptz-panel"}}
// PanLeftButton is the button for panning left preview.
PanLeftButton = UIComponent{"pan left button", []string{"#pan-left"}}
// PanRightButton is the button for panning right preview.
PanRightButton = UIComponent{"pan right button", []string{"#pan-right"}}
// TiltUpButton is the button for tilting up preview.
TiltUpButton = UIComponent{"tilt up button", []string{"#tilt-up"}}
// TiltDownButton is the button for tilting down preview.
TiltDownButton = UIComponent{"tilt down button", []string{"#tilt-down"}}
// ZoomInButton is the button for zoom in preview.
ZoomInButton = UIComponent{"zoom in button", []string{"#zoom-in"}}
// ZoomOutButton is the button for zoom out preview.
ZoomOutButton = UIComponent{"zoom out button", []string{"#zoom-out"}}
// PTZResetAllButton is the button for reset PTZ to default value.
PTZResetAllButton = UIComponent{"ptz reset all button", []string{"#ptz-reset-all"}}
// SquareModeButton is the button to enter square mode.
SquareModeButton = UIComponent{"square mode button", []string{".mode-item>input[data-mode=\"square\"]"}}
// ScanModeButton is the button to enter scan mode.
ScanModeButton = UIComponent{"scan mode button", []string{".mode-item>input[data-mode=\"scan\"]"}}
// ScanBarcodeOption is the option button to switch to QR code detection mode in scan mode.
ScanBarcodeOption = UIComponent{"scan barcode option", []string{"#scan-barcode"}}
// ScanDocumentModeOption is the document mode option of scan mode.
ScanDocumentModeOption = UIComponent{"document mode button", []string{"#scan-document"}}
// ReviewView is the review view after taking a photo under document mode.
ReviewView = UIComponent{"document review view", []string{"#view-review"}}
// ReviewImage is the image to be reviewed.
ReviewImage = UIComponent{"reivew image", []string{"#view-review .review-image"}}
// SaveAsPDFButton is the button to save document as PDF.
SaveAsPDFButton = UIComponent{"save document as pdf button", []string{"#view-review button[i18n-text=label_save_pdf_document]"}}
// SaveAsPhotoButton is the button to save document as photo.
SaveAsPhotoButton = UIComponent{"save document as photo button", []string{"#view-review button[i18n-text=label_save_photo_document]"}}
// RetakeButton is the button to retake the document photo.
RetakeButton = UIComponent{"retake document photo button", []string{
// TODO(b/203028477): Remove selector for old mode name after
// naming CL on app side fully landed.
"#review-retake", "#view-review button[i18n-text=label_retake]"}}
// FixCropButton is the button to fix document crop area.
FixCropButton = UIComponent{"fix document crop area button", []string{"#view-review button[i18n-text=label_fix_document]"}}
// CropDocumentView is the view for fix document crop area.
CropDocumentView = UIComponent{"crop document view", []string{"#view-crop-document"}}
// CropDocumentImage is the image to be cropped document from.
CropDocumentImage = UIComponent{"crop document image", []string{"#view-crop-document .review-image"}}
// CropDoneButton is the button clicked after fix document crop area.
CropDoneButton = UIComponent{"crop document done button", []string{"#view-crop-document button[i18n-text=label_crop_done]"}}
// DocumentCorner is the dragging point of document corner in crop area page.
DocumentCorner = UIComponent{"document corner dragging point", []string{"#view-crop-document .dot"}}
// DocumentCornerOverlay is the overlay that CCA used to draw document corners on.
DocumentCornerOverlay = UIComponent{"document corner overlay", []string{
"#preview-document-corner-overlay"}}
// GifRecordingOption is the radio button to toggle gif recording option.
GifRecordingOption = UIComponent{"gif recording button", []string{
"input[type=radio][data-state=record-type-gif]"}}
// GifReviewSaveButton is the save button in gif review page.
GifReviewSaveButton = UIComponent{"save gif button", []string{
"#view-review button[i18n-text=label_save]"}}
// GifReviewRetakeButton is the retake button in gif review page.
GifReviewRetakeButton = UIComponent{"retake gif button", []string{"#review-retake"}}
// FrontAspectRatioOptions are the buttons of aspect ratio options for the front camera.
FrontAspectRatioOptions = UIComponent{"front aspect ratio options", []string{"#view-photo-aspect-ratio-settings .menu-item>input[data-facing=\"user\"]"}}
// BackAspectRatioOptions are the buttons of aspect ratio options for the back camera.
BackAspectRatioOptions = UIComponent{"back aspect ratio options", []string{"#view-photo-aspect-ratio-settings .menu-item>input[data-facing=\"environment\"]"}}
// FrontPhotoResolutionOptions are the buttons of photo resolution options for the front camera.
FrontPhotoResolutionOptions = UIComponent{"front photo resolution options", []string{"#view-photo-resolution-settings .menu-item>input[data-facing=\"user\"]"}}
// BackPhotoResolutionOptions are the buttons of photo resolution options for the back camera.
BackPhotoResolutionOptions = UIComponent{"back photo resolution options", []string{"#view-photo-resolution-settings .menu-item>input[data-facing=\"environment\"]"}}
// FrontVideoResolutionOptions are the buttons of video resolution options for the front camera.
FrontVideoResolutionOptions = UIComponent{"front video resolution options", []string{"#view-video-resolution-settings .menu-item>input[data-facing=\"user\"]"}}
// BackVideoResolutionOptions are the buttons of video resolution options for the back camera.
BackVideoResolutionOptions = UIComponent{"back video resolution options", []string{"#view-video-resolution-settings .menu-item>input[data-facing=\"environment\"]"}}
)
// Option is the option for toggling state.
type Option struct {
// ui is the |UIComponent| to toggle the option.
ui UIComponent
// state is state toggle by this option.
state string
}
func newOption(state, selector string) Option {
name := fmt.Sprintf("option to toggle %v state", state)
selectors := []string{selector}
return Option{ui: UIComponent{Name: name, Selectors: selectors}, state: state}
}
var (
// CustomVideoParametersOption is the option to enable custom video parameters.
CustomVideoParametersOption = newOption("custom-video-parameters", "#custom-video-parameters")
// ExpertModeOption is the option to enable expert mode.
ExpertModeOption = newOption("expert", "#expert-enable-expert-mode")
// GridOption is the option to show grid lines on preview.
GridOption = newOption("grid", "#toggle-grid")
// MirrorOption is the option to flip preview horizontally.
MirrorOption = newOption("mirror", "#toggle-mirror")
// SaveMetadataOption is the option to save metadata of capture result.
SaveMetadataOption = newOption("save-metadata", "#expert-save-metadata")
// ShowMetadataOption is the option to show preview metadata.
ShowMetadataOption = newOption("show-metadata", "#expert-show-metadata")
// EnableDocumentModeOnAllCamerasOption is the option to enable document scanning on all cameras.
EnableDocumentModeOnAllCamerasOption = newOption("enable-document-mode-on-all-cameras", "#expert-enable-document-mode-on-all-cameras")
// EnableMultistreamRecordingOption is the option to enable document scanning on all cameras.
EnableMultistreamRecordingOption = newOption("enable-multistream-recording", "#expert-enable-multistream-recording")
// ScanBarcodeOptionInPhotoMode is the option to enable barcode scanning in photo mode.
ScanBarcodeOptionInPhotoMode = newOption("enable-scan-barcode", "#toggle-barcode")
// ShowGifRecordingOption is the option to enable gif recording.
ShowGifRecordingOption = newOption("show-gif-recording-option", "#expert-enable-gif-recording")
// TimerOption is the option to enable countdown timer.
TimerOption = newOption("timer", "#toggle-timer")
)
type errorUINotExist struct {
ui *UIComponent
}
func (err errorUINotExist) Error() string {
return fmt.Sprintf("failed to resolved ui %v to its correct selector", err.ui.Name)
}
// IsUINotExist returns true if the given error is from errorUINotExist error type.
func IsUINotExist(err error) bool {
if err == nil {
return false
}
if _, ok := err.(errorUINotExist); ok {
return true
}
if wrappedErr, ok := err.(*errors.E); ok {
return IsUINotExist(wrappedErr.Unwrap())
}
return false
}
// HasClass returns true if the given HTML element has the given class name.
func (a *App) HasClass(ctx context.Context, ui UIComponent, className string) (bool, error) {
selector, err := a.resolveUISelector(ctx, ui)
if err != nil {
return false, errors.Wrapf(err, "failed to get the selector of UI: %v", ui.Name)
}
var result bool
if err := a.conn.Call(ctx, &result, "Tast.hasClass", selector, className); err != nil {
return false, errors.Wrapf(err, "failed to check class for UI: %v and class name: %v", ui.Name, className)
}
return result, nil
}
// resolveUISelector resolves ui to its correct selector.
func (a *App) resolveUISelector(ctx context.Context, ui UIComponent) (string, error) {
for _, s := range ui.Selectors {
if exist, err := a.selectorExist(ctx, s); err != nil {
return "", err
} else if exist {
return s, nil
}
}
return "", errorUINotExist{ui: &ui}
}
// Style returns the value of an CSS attribute of an UI component.
func (a *App) Style(ctx context.Context, ui UIComponent, attribute string) (string, error) {
selector, err := a.resolveUISelector(ctx, ui)
if err != nil {
return "", errors.Wrapf(err, "failed to get the selector of UI: %v", ui.Name)
}
var style string
if err := a.conn.Call(ctx, &style, "Tast.getStyle", selector, attribute); err != nil {
return "", errors.Wrapf(err, "failed to get the style of attribute: %v of UI: %v", attribute, ui.Name)
}
return style, nil
}
// Exist returns whether a UI component exists.
func (a *App) Exist(ctx context.Context, ui UIComponent) (bool, error) {
_, err := a.resolveUISelector(ctx, ui)
if err != nil {
if IsUINotExist(err) {
return false, nil
}
return false, err
}
return true, nil
}
// OptionExist returns if the option exists.
func (a *App) OptionExist(ctx context.Context, option Option) (bool, error) {
return a.Exist(ctx, option.ui)
}
// Visible returns whether a UI component is visible on the screen.
func (a *App) Visible(ctx context.Context, ui UIComponent) (bool, error) {
wrapError := func(err error) error {
return errors.Wrapf(err, "failed to check visibility state of %v", ui.Name)
}
selector, err := a.resolveUISelector(ctx, ui)
if err != nil {
return false, wrapError(err)
}
var visible bool
if err := a.conn.Call(ctx, &visible, "Tast.isVisible", selector); err != nil {
return false, wrapError(err)
}
return visible, nil
}
// CheckVisible returns an error if visibility state of ui is not expected.
func (a *App) CheckVisible(ctx context.Context, ui UIComponent, expected bool) error {
if visible, err := a.Visible(ctx, ui); err != nil {
return err
} else if visible != expected {
return errors.Errorf("unexpected %v visibility state: got %v, want %v", ui.Name, visible, expected)
}
return nil
}
// WaitForVisibleState waits until the visibility of ui becomes expected.
func (a *App) WaitForVisibleState(ctx context.Context, ui UIComponent, expected bool) error {
return testing.Poll(ctx, func(ctx context.Context) error {
visible, err := a.Visible(ctx, ui)
if err != nil {
return testing.PollBreak(err)
}
if visible != expected {
return errors.Errorf("failed to wait visibility state for %v: got %v, want %v", ui.Name, visible, expected)
}
return nil
}, &testing.PollOptions{Timeout: 5 * time.Second})
}
// Disabled returns disabled attribute of HTMLElement of |ui|.
func (a *App) Disabled(ctx context.Context, ui UIComponent) (bool, error) {
selector, err := a.resolveUISelector(ctx, ui)
if err != nil {
return false, errors.Wrapf(err, "failed to resolve ui %v to correct selector", ui.Name)
}
var disabled bool
if err := a.conn.Call(ctx, &disabled, "(selector) => document.querySelector(selector).disabled", selector); err != nil {
return false, errors.Wrapf(err, "failed to get disabled state of %v", ui.Name)
}
return disabled, nil
}
// WaitForDisabled waits until the disabled state of ui becomes |expected|.
func (a *App) WaitForDisabled(ctx context.Context, ui UIComponent, expected bool) error {
return testing.Poll(ctx, func(ctx context.Context) error {
disabled, err := a.Disabled(ctx, ui)
if err != nil {
return testing.PollBreak(errors.Wrapf(err, "failed to wait disabled state of %v to be %v", ui.Name, expected))
}
if disabled != expected {
return errors.Errorf("failed to wait disabled state for %v: got %v, want %v", ui.Name, disabled, expected)
}
return nil
}, &testing.PollOptions{Timeout: 5 * time.Second})
}
// CountUI returns the number of ui element.
func (a *App) CountUI(ctx context.Context, ui UIComponent) (int, error) {
wrapError := func(err error) error {
return errors.Wrapf(err, "failed to count number of %v", ui.Name)
}
selector, err := a.resolveUISelector(ctx, ui)
if err != nil {
return 0, wrapError(err)
}
var number int
if err := a.conn.Call(ctx, &number, `(selector) => document.querySelectorAll(selector).length`, selector); err != nil {
return 0, wrapError(err)
}
return number, nil
}
// AttributeWithIndex returns the attr attribute of the index th ui.
func (a *App) AttributeWithIndex(ctx context.Context, ui UIComponent, index int, attr string) (string, error) {
wrapError := func(err error) error {
return errors.Wrapf(err, "failed to get %v attribute of %v th %v", attr, index, ui.Name)
}
selector, err := a.resolveUISelector(ctx, ui)
if err != nil {
return "", wrapError(err)
}
var value string
if err := a.conn.Call(
ctx, &value,
`(selector, index, attr) => document.querySelectorAll(selector)[index].getAttribute(attr)`,
selector, index, attr); err != nil {
return "", wrapError(err)
}
return value, nil
}
// ScreenXYWithIndex returns the screen coordinates of the left-top corner of the |index|'th |ui|.
func (a *App) ScreenXYWithIndex(ctx context.Context, ui UIComponent, index int) (*coords.Point, error) {
wrapError := func(err error) error {
return errors.Wrapf(err, "failed to get screen coordindates of %v th %v", index, ui.Name)
}
selector, err := a.resolveUISelector(ctx, ui)
if err != nil {
return nil, wrapError(err)
}
var pt coords.Point
if err := a.conn.Call(ctx, &pt, `Tast.getScreenXY`, selector, index); err != nil {
return nil, wrapError(err)
}
return &pt, nil
}
// Size returns size of the |ui|.
func (a *App) Size(ctx context.Context, ui UIComponent) (*Resolution, error) {
wrapError := func(err error) error {
return errors.Wrapf(err, "failed to get size of %v", ui.Name)
}
selector, err := a.resolveUISelector(ctx, ui)
if err != nil {
return nil, wrapError(err)
}
var size Resolution
if err := a.conn.Call(ctx, &size, `Tast.getSize`, selector); err != nil {
return nil, wrapError(err)
}
return &size, nil
}
// Click clicks on ui.
func (a *App) Click(ctx context.Context, ui UIComponent) error {
wrapError := func(err error) error {
return errors.Wrapf(err, "failed to click on %v", ui.Name)
}
selector, err := a.resolveUISelector(ctx, ui)
if err != nil {
return wrapError(err)
}
if err := a.ClickWithSelector(ctx, selector); err != nil {
return wrapError(err)
}
return nil
}
// ClickWithIndex clicks nth ui.
func (a *App) ClickWithIndex(ctx context.Context, ui UIComponent, index int) error {
selector, err := a.resolveUISelector(ctx, ui)
if err != nil {
return err
}
if err := a.conn.Call(ctx, nil, `(selector, index) => document.querySelectorAll(selector)[index].click()`, selector, index); err != nil {
return errors.Wrapf(err, "failed to click on %v(th) %v", index, ui.Name)
}
return nil
}
// ClickChildIfContain clicks the child which contains the given string in its text content.
func (a *App) ClickChildIfContain(ctx context.Context, ui UIComponent, text string) error {
selector, err := a.resolveUISelector(ctx, ui)
if err != nil {
return err
}
if err := a.conn.Call(ctx, nil, `(selector, text) => {
const element = document.querySelector(selector);
const children = element.childNodes;
const matches = [...children].filter((node) => node.textContent.includes(text));
for (const match of matches) {
match.click();
}
}`, selector, text); err != nil {
return errors.Wrapf(err, "failed to click children of %v containing text: %v", ui.Name, text)
}
return nil
}
// Hold holds on |ui| by sending pointerdown and pointerup for |d| duration.
func (a *App) Hold(ctx context.Context, ui UIComponent, d time.Duration) error {
wrapError := func(err error) error {
return errors.Wrapf(err, "failed to hold on %v", ui.Name)
}
selector, err := a.resolveUISelector(ctx, ui)
if err != nil {
return wrapError(err)
}
return a.conn.Call(ctx, nil, `Tast.hold`, selector, d.Milliseconds())
}
// ClickPTZButton clicks on PTZ Button.
func (a *App) ClickPTZButton(ctx context.Context, ui UIComponent) error {
// Hold for 0ms to trigger PTZ minimal step movement.
return a.Hold(ctx, ui, 0)
}
// IsCheckedWithIndex gets checked state of nth ui.
func (a *App) IsCheckedWithIndex(ctx context.Context, ui UIComponent, index int) (bool, error) {
selector, err := a.resolveUISelector(ctx, ui)
if err != nil {
return false, err
}
var checked bool
if err := a.conn.Call(ctx, &checked, `(selector, index) => document.querySelectorAll(selector)[index].checked`, selector, index); err != nil {
return false, errors.Wrapf(err, "failed to get checked state on %v(th) %v", index, ui.Name)
}
return checked, nil
}
// SelectOption selects the target option in HTMLSelectElement.
func (a *App) SelectOption(ctx context.Context, ui UIComponent, value string) error {
if err := a.WaitForVisibleState(ctx, ui, true); err != nil {
return err
}
selector, err := a.resolveUISelector(ctx, ui)
if err != nil {
return err
}
return a.conn.Call(ctx, nil, "Tast.selectOption", selector, value)
}
// InputRange returns the range of valid value for range type input element.
func (a *App) InputRange(ctx context.Context, ui UIComponent) (*Range, error) {
if err := a.WaitForVisibleState(ctx, ui, true); err != nil {
return nil, err
}
selector, err := a.resolveUISelector(ctx, ui)
if err != nil {
return nil, err
}
var r Range
if err := a.conn.Call(ctx, &r, "Tast.getInputRange", selector); err != nil {
return nil, errors.Wrapf(err, "failed to get input range of %v", ui.Name)
}
return &r, nil
}
// SetRangeInput set value of range input.
func (a *App) SetRangeInput(ctx context.Context, ui UIComponent, value int) error {
if err := a.WaitForVisibleState(ctx, ui, true); err != nil {
return err
}
selector, err := a.resolveUISelector(ctx, ui)
if err != nil {
return err
}
if err := a.conn.Call(ctx, nil, "Tast.setInputValue", selector, value); err != nil {
return errors.Wrapf(err, "failed to set range input %v to %v", ui.Name, value)
}
return nil
}